Merge branch 'staging-fixes' into staging
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index a9aa5ae..bb94383 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '10.1.68'
+__version__ = '10.1.70'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index aec3d1b..f213ffa 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -933,7 +933,7 @@
 	return paid_amount[0][0] if paid_amount else 0
 
 @frappe.whitelist()
-def get_party_and_account_balance(company, date, paid_from, paid_to=None, ptype=None, pty=None, cost_center=None):
+def get_party_and_account_balance(company, date, paid_from=None, paid_to=None, ptype=None, pty=None, cost_center=None):
 	return frappe._dict({
 		"party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
 		"paid_from_account_balance": get_balance_on(paid_from, date, cost_center=cost_center),
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 42f371b..fe99763 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -201,8 +201,8 @@
 				"discount_percentage": 0.0
 			})
 		else:
-			item_details.discount_percentage = pricing_rule.discount_percentage or args.discount_percentage
-
+			item_details.discount_percentage = (pricing_rule.get('discount_percentage', 0)
+				if pricing_rule else args.discount_percentage)
 	elif args.get('pricing_rule'):
 		item_details = remove_pricing_rule_for_item(args.get("pricing_rule"), item_details)
 
@@ -393,4 +393,4 @@
 	doc.selling = 1 if doctype == "Customer" else 0
 	doc.buying = 1 if doctype == "Supplier" else 0
 
-	return doc
\ No newline at end of file
+	return doc
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 1b63f71..6387003 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -662,9 +662,6 @@
 	def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
 		auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
 
-		if not self.grand_total:
-			return
-
 		if not gl_entries:
 			gl_entries = self.get_gl_entries()
 
diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
index 2508a1f..bd2c34b 100644
--- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
+++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
@@ -21,6 +21,8 @@
 	party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
 	if filters.get('party_type') == 'Student':
 		party_name_field = 'first_name'
+	elif filters.get('party_type') == 'Shareholder':
+		party_name_field = 'title'
 
 	party_filters = {"name": filters.get("party")} if filters.get("party") else {}
 	parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], 
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e277602..51747f6 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -117,6 +117,13 @@
 			if self.get("group_same_items"):
 				self.group_similar_items()
 
+			df = self.meta.get_field("discount_amount")
+			if self.get("discount_amount") and hasattr(self, "taxes") and not len(self.taxes):
+				df.set("print_hide", 0)
+				self.discount_amount = -self.discount_amount
+			else:
+				df.set("print_hide", 1)
+
 	def validate_paid_amount(self):
 		if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
 			is_paid = self.get("is_pos") or self.get("is_paid")
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 57c6556..719f4c7 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -345,7 +345,8 @@
 			sales_orders = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)]))
 			if sales_orders:
 				po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', sales_orders)})
-				self.po_no = ', '.join(list(set([d.po_no for d in po_nos if d.po_no])))
+				if po_nos and po_nos[0].get('po_no'):
+					self.po_no = ', '.join(list(set([d.po_no for d in po_nos if d.po_no])))
 
 	def validate_items(self):
 		# validate items to see if they have is_sales_item enabled
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 2005147..87b7942 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -11,8 +11,8 @@
 app_license = "GNU General Public License (v3)"
 source_link = "https://github.com/frappe/erpnext"
 
-develop_version = '11.x.x-develop'
-staging_version = '11.0.3-beta.19'
+develop_version = '12.x.x-develop'
+staging_version = '11.0.3-beta.20'
 
 error_report_email = "support@erpnext.com"
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index a55c3c5..8c5f2af 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -54,7 +54,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "depends_on": "eval:!doc.__islocal", 
+   "depends_on": "", 
    "fieldname": "item_name", 
    "fieldtype": "Data", 
    "hidden": 0, 
@@ -1976,7 +1976,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-10-11 11:52:39.047935", 
+ "modified": "2018-10-24 02:07:21.618275", 
  "modified_by": "Administrator", 
  "module": "Manufacturing", 
  "name": "BOM", 
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.js b/erpnext/setup/doctype/global_defaults/global_defaults.js
index a78461d..58b8c51 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.js
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.js
@@ -1,10 +1,35 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
 $.extend(cur_frm.cscript, {
-	validate: function(doc, cdt, cdn) {
-		return $c_obj(doc, 'get_defaults', '', function(r, rt){
+	onload: function (doc, cdt, cdn) {
+		cur_frm.trigger("get_distance_uoms");
+	},
+
+	validate: function (doc, cdt, cdn) {
+		return $c_obj(doc, 'get_defaults', '', function (r, rt) {
 			frappe.sys_defaults = r.message;
 		});
+	},
+
+	get_distance_uoms: function (frm) {
+		let units = [];
+
+		frappe.call({
+			method: "frappe.client.get_list",
+			args: {
+				doctype: "UOM Conversion Factor",
+				filters: { "category": "Length" },
+				fields: ["to_uom"],
+				limit_page_length: 500
+			},
+			callback: function (r) {
+				r.message.forEach(row => units.push(row.to_uom));
+			}
+		});
+
+		cur_frm.set_query("default_distance_unit", function (doc) {
+			return { filters: { "name": ["IN", units] } };
+		})
 	}
 });
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json
index a6c5964..bafb97a 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.json
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.json
@@ -1,122 +1,183 @@
 {
  "allow_copy": 1, 
- "allow_email": 0, 
+ "allow_guest_to_view": 0, 
  "allow_import": 0, 
- "allow_print": 0, 
  "allow_rename": 0, 
- "allow_trash": 0, 
+ "beta": 0, 
  "creation": "2013-05-02 17:53:24", 
  "custom": 0, 
  "docstatus": 0, 
  "doctype": "DocType", 
+ "editable_grid": 0, 
  "fields": [
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "default_company", 
    "fieldtype": "Link", 
    "hidden": 0, 
    "ignore_user_permissions": 1, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
+   "in_standard_filter": 0, 
    "label": "Default Company", 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "options": "Company", 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "current_fiscal_year", 
    "fieldtype": "Link", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
+   "in_standard_filter": 0, 
    "label": "Current Fiscal Year", 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "options": "Fiscal Year", 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "country", 
    "fieldtype": "Link", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
+   "in_standard_filter": 0, 
    "label": "Country", 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "options": "Country", 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
+   "default": "", 
+   "fieldname": "default_distance_unit", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Default Distance Unit", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "UOM", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "column_break_8", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
+   "in_standard_filter": 0, 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "default": "INR", 
    "fieldname": "default_currency", 
    "fieldtype": "Link", 
@@ -124,26 +185,32 @@
    "ignore_user_permissions": 1, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 1, 
+   "in_standard_filter": 0, 
    "label": "Default Currency", 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "options": "Currency", 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "description": "Do not show any symbol like $ etc next to currencies.", 
    "fieldname": "hide_currency_symbol", 
    "fieldtype": "Select", 
@@ -151,26 +218,32 @@
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 1, 
+   "in_standard_filter": 0, 
    "label": "Hide Currency Symbol", 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "options": "\nNo\nYes", 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "description": "If disable, 'Rounded Total' field will not be visible in any transaction", 
    "fieldname": "disable_rounded_total", 
    "fieldtype": "Check", 
@@ -178,25 +251,31 @@
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 1, 
+   "in_standard_filter": 0, 
    "label": "Disable Rounded Total", 
    "length": 0, 
-   "no_column": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "columns": 0, 
    "description": "If disable, 'In Words' field will not be visible in any transaction", 
    "fieldname": "disable_in_words", 
    "fieldtype": "Check", 
@@ -204,7 +283,9 @@
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 1, 
+   "in_standard_filter": 0, 
    "label": "Disable In Words", 
    "length": 0, 
    "no_copy": 0, 
@@ -213,26 +294,28 @@
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }
  ], 
+ "has_web_view": 0, 
  "hide_heading": 0, 
  "hide_toolbar": 0, 
  "icon": "fa fa-cog", 
  "idx": 1, 
+ "image_view": 0, 
  "in_create": 1, 
-
  "is_submittable": 0, 
- "is_transaction_doc": 0, 
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
  "menu_index": 0, 
- "modified": "2016-03-03 16:14:41.260467", 
+ "modified": "2018-10-15 03:08:19.886212", 
  "modified_by": "Administrator", 
  "module": "Setup", 
  "name": "Global Defaults", 
@@ -240,7 +323,6 @@
  "permissions": [
   {
    "amend": 0, 
-   "apply_user_permissions": 0, 
    "cancel": 0, 
    "create": 1, 
    "delete": 0, 
@@ -252,7 +334,6 @@
    "print": 0, 
    "read": 1, 
    "report": 0, 
-   "restrict": 0, 
    "role": "System Manager", 
    "set_user_permissions": 0, 
    "share": 1, 
@@ -260,10 +341,12 @@
    "write": 1
   }
  ], 
+ "quick_entry": 0, 
  "read_only": 1, 
  "read_only_onload": 0, 
- "show_in_menu": 0, 
+ "show_name_in_global_search": 0, 
  "sort_order": "DESC", 
- "use_template": 0, 
- "version": 0
+ "track_changes": 0, 
+ "track_seen": 0, 
+ "track_views": 0
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/global_defaults/test_global_defaults.js b/erpnext/setup/doctype/global_defaults/test_global_defaults.js
new file mode 100644
index 0000000..33634eb
--- /dev/null
+++ b/erpnext/setup/doctype/global_defaults/test_global_defaults.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Global Defaults", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new Global Defaults
+		() => frappe.tests.make('Global Defaults', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/setup/doctype/global_defaults/test_global_defaults.py b/erpnext/setup/doctype/global_defaults/test_global_defaults.py
new file mode 100644
index 0000000..0495af7
--- /dev/null
+++ b/erpnext/setup/doctype/global_defaults/test_global_defaults.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestGlobalDefaults(unittest.TestCase):
+	pass
diff --git a/erpnext/stock/doctype/delivery_settings/delivery_settings.json b/erpnext/stock/doctype/delivery_settings/delivery_settings.json
index b70d74d..963403b 100644
--- a/erpnext/stock/doctype/delivery_settings/delivery_settings.json
+++ b/erpnext/stock/doctype/delivery_settings/delivery_settings.json
@@ -143,6 +143,70 @@
    "set_only_once": 0, 
    "translatable": 0, 
    "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "cb_delivery", 
+   "fieldtype": "Column Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "description": "In minutes", 
+   "fieldname": "stop_delay", 
+   "fieldtype": "Int", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Delay between Delivery Stops", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
   }
  ], 
  "has_web_view": 0, 
@@ -155,7 +219,7 @@
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-09-05 00:16:23.569855", 
+ "modified": "2018-09-09 23:51:34.279941", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Delivery Settings", 
diff --git a/erpnext/stock/doctype/delivery_stop/delivery_stop.json b/erpnext/stock/doctype/delivery_stop/delivery_stop.json
index 023e34d..7bce72d 100644
--- a/erpnext/stock/doctype/delivery_stop/delivery_stop.json
+++ b/erpnext/stock/doctype/delivery_stop/delivery_stop.json
@@ -500,6 +500,38 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "distance", 
+   "fieldtype": "Float", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Distance", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "2", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "estimated_arrival", 
    "fieldtype": "Datetime", 
    "hidden": 0, 
@@ -532,6 +564,168 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "lat", 
+   "fieldtype": "Float", 
+   "hidden": 1, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Latitude", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "column_break_19", 
+   "fieldtype": "Column Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "default": "", 
+   "depends_on": "eval:doc.distance", 
+   "fieldname": "uom", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "UOM", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "UOM", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "lng", 
+   "fieldtype": "Float", 
+   "hidden": 1, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Longitude", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "more_information_section", 
+   "fieldtype": "Section Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "More Information", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "details", 
    "fieldtype": "Text Editor", 
    "hidden": 0, 
@@ -568,7 +762,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-09-05 00:51:55.275009", 
+ "modified": "2018-10-11 22:32:27.450906", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Delivery Stop", 
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
index 9c31e05..a38c6d7 100755
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
@@ -65,31 +65,43 @@
 	},
 
 	calculate_arrival_time: function (frm) {
-		frappe.call({
-			method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.get_arrival_times',
-			freeze: true,
-			freeze_message: __("Updating estimated arrival times."),
-			args: {
-				name: frm.doc.name,
-			},
-			callback: function (r) {
-				frm.reload_doc();
+		frappe.db.get_value("Google Maps Settings", { name: "Google Maps Settings" }, "enabled", (r) => {
+			if (r.enabled == 0) {
+				frappe.throw(__("Please enable Google Maps Settings to estimate and optimize routes"));
+			} else {
+				frappe.call({
+					method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.get_arrival_times',
+					freeze: true,
+					freeze_message: __("Updating estimated arrival times."),
+					args: {
+						delivery_trip: frm.doc.name,
+					},
+					callback: function (r) {
+						frm.reload_doc();
+					}
+				});
 			}
-		});
+		})
 	},
 
 	optimize_route: function (frm) {
-		frappe.call({
-			method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.optimize_route',
-			freeze: true,
-			freeze_message: __("Optimizing routes."),
-			args: {
-				name: frm.doc.name,
-			},
-			callback: function (r) {
-				frm.reload_doc();
+		frappe.db.get_value("Google Maps Settings", {name: "Google Maps Settings"}, "enabled", (r) => {
+			if (r.enabled == 0) {
+				frappe.throw(__("Please enable Google Maps Settings to estimate and optimize routes"));
+			} else {
+				frappe.call({
+					method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.optimize_route',
+					freeze: true,
+					freeze_message: __("Optimizing routes."),
+					args: {
+						delivery_trip: frm.doc.name,
+					},
+					callback: function (r) {
+						frm.reload_doc();
+					}
+				});
 			}
-		});
+		})
 	},
 
 	notify_customers: function (frm) {
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
index 3cb19af..a9236e8 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
@@ -159,101 +159,7 @@
    "in_global_search": 0, 
    "in_list_view": 0, 
    "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "departure_time", 
-   "fieldtype": "Time", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Departure Time", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_4", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
+   "label": "Delivery Details", 
    "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
@@ -343,6 +249,104 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "total_distance", 
+   "fieldtype": "Float", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Total Estimated Distance", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "2", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "default": "", 
+   "depends_on": "eval:doc.total_distance", 
+   "fieldname": "uom", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Distance UOM", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "UOM", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "column_break_4", 
+   "fieldtype": "Column Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "vehicle", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -376,6 +380,38 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "departure_time", 
+   "fieldtype": "Datetime", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Departure Time", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 1, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "delivery_service_stops", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -441,7 +477,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "depends_on": "eval:!cur_frm.is_new()", 
+   "depends_on": "eval:!doc.__islocal", 
    "fieldname": "calculate_arrival_time", 
    "fieldtype": "Button", 
    "hidden": 0, 
@@ -474,6 +510,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "eval:!doc.__islocal", 
    "fieldname": "optimize_route", 
    "fieldtype": "Button", 
    "hidden": 0, 
@@ -574,7 +611,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-09-05 01:20:34.165834", 
+ "modified": "2018-10-11 22:32:04.355068", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Delivery Trip", 
@@ -627,5 +664,6 @@
  "sort_field": "modified", 
  "sort_order": "DESC", 
  "track_changes": 0, 
- "track_seen": 0
+ "track_seen": 0, 
+ "track_views": 0
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 427bc24..431eb6c 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -10,17 +10,43 @@
 from frappe import _
 from frappe.contacts.doctype.address.address import get_address_display
 from frappe.model.document import Document
-from frappe.utils import get_datetime, get_link_to_form, cstr
+from frappe.utils import cint, get_datetime, get_link_to_form
 
 
 class DeliveryTrip(Document):
+	def __init__(self, *args, **kwargs):
+		super(DeliveryTrip, self).__init__(*args, **kwargs)
+
+		# Google Maps returns distances in meters by default
+		self.default_distance_uom = frappe.db.get_single_value("Global Defaults", "default_distance_unit") or "Meter"
+		self.uom_conversion_factor = frappe.db.get_value("UOM Conversion Factor",
+														{"from_uom": "Meter", "to_uom": self.default_distance_uom},
+														"value")
+
+	def validate(self):
+		self.validate_stop_addresses()
+
 	def on_submit(self):
 		self.update_delivery_notes()
 
 	def on_cancel(self):
 		self.update_delivery_notes(delete=True)
 
+	def validate_stop_addresses(self):
+		for stop in self.delivery_stops:
+			if not stop.customer_address:
+				stop.customer_address = get_address_display(frappe.get_doc("Address", stop.address).as_dict())
+
 	def update_delivery_notes(self, delete=False):
+		"""
+		Update all connected Delivery Notes with Delivery Trip details
+		(Driver, Vehicle, etc.). If `delete` is `True`, then details
+		are removed.
+
+		Args:
+			delete (bool, optional): Defaults to `False`. `True` if driver details need to be emptied, else `False`.
+		"""
+
 		delivery_notes = list(set([stop.delivery_note for stop in self.delivery_stops if stop.delivery_note]))
 
 		update_fields = {
@@ -28,7 +54,7 @@
 			"driver_name": self.driver_name,
 			"vehicle_no": self.vehicle,
 			"lr_no": self.name,
-			"lr_date": self.date
+			"lr_date": self.departure_time
 		}
 
 		for delivery_note in delivery_notes:
@@ -44,58 +70,175 @@
 		delivery_notes = [get_link_to_form("Delivery Note", note) for note in delivery_notes]
 		frappe.msgprint(_("Delivery Notes {0} updated".format(", ".join(delivery_notes))))
 
+	def process_route(self, optimize):
+		"""
+		Estimate the arrival times for each stop in the Delivery Trip.
+		If `optimize` is True, the stops will be re-arranged, based
+		on the optimized order, before estimating the arrival times.
+
+		Args:
+			optimize (bool): True if route needs to be optimized, else False
+		"""
+
+		if not frappe.db.get_single_value("Google Maps Settings", "enabled"):
+			frappe.throw(_("Cannot process route, since Google Maps Settings is disabled."))
+
+		departure_datetime = get_datetime(self.departure_time)
+		route_list = self.form_route_list(optimize)
+
+		# For locks, maintain idx count while looping through route list
+		idx = 0
+		for route in route_list:
+			directions = get_directions(route, optimize)
+
+			if directions:
+				if optimize and len(directions.get("waypoint_order")) > 1:
+					self.rearrange_stops(directions.get("waypoint_order"), start=idx)
+
+				# Avoid estimating last leg back to the home address
+				legs = directions.get("legs")[:-1] if route == route_list[-1] else directions.get("legs")
+
+				# Google Maps returns the legs in the optimized order
+				for leg in legs:
+					delivery_stop = self.delivery_stops[idx]
+
+					delivery_stop.lat, delivery_stop.lng = leg.get("end_location", {}).values()
+					delivery_stop.uom = self.default_distance_uom
+					distance = leg.get("distance", {}).get("value", 0.0)  # in meters
+					delivery_stop.distance = distance * self.uom_conversion_factor
+
+					duration = leg.get("duration", {}).get("value", 0)
+					estimated_arrival = departure_datetime + datetime.timedelta(seconds=duration)
+					delivery_stop.estimated_arrival = estimated_arrival
+
+					stop_delay = frappe.db.get_single_value("Delivery Settings", "stop_delay")
+					departure_datetime = estimated_arrival + datetime.timedelta(minutes=cint(stop_delay))
+					idx += 1
+
+				# Include last leg in the final distance calculation
+				self.uom = self.default_distance_uom
+				total_distance = sum([leg.get("distance", {}).get("value", 0.0)
+											for leg in directions.get("legs")])  # in meters
+				self.total_distance = total_distance * self.uom_conversion_factor
+			else:
+				idx += len(route) - 1
+
+		self.save()
+
+	def form_route_list(self, optimize):
+		"""
+		Form a list of address routes based on the delivery stops. If locks
+		are present, and the routes need to be optimized, then they will be
+		split into sublists at the specified lock position(s).
+
+		Args:
+			optimize (bool): `True` if route needs to be optimized, else `False`
+
+		Returns:
+			(list of list of str): List of address routes split at locks, if optimize is `True`
+		"""
+
+		settings = frappe.get_single("Google Maps Settings")
+		home_address = get_address_display(frappe.get_doc("Address", settings.home_address).as_dict())
+
+		route_list = []
+		# Initialize first leg with origin as the home address
+		leg = [home_address]
+
+		for stop in self.delivery_stops:
+			leg.append(stop.customer_address)
+
+			if optimize and stop.lock:
+				route_list.append(leg)
+				leg = [stop.customer_address]
+
+		# For last leg, append home address as the destination
+		# only if lock isn't on the final stop
+		if len(leg) > 1:
+			leg.append(home_address)
+			route_list.append(leg)
+
+		route_list = [[sanitize_address(address) for address in route] for route in route_list]
+
+		return route_list
+
+	def rearrange_stops(self, optimized_order, start):
+		"""
+		Re-arrange delivery stops based on order optimized
+		for vehicle routing problems.
+
+		Args:
+			optimized_order (list of int): The index-based optimized order of the route
+			start (int): The index at which to start the rearrangement
+		"""
+
+		stops_order = []
+
+		# Child table idx starts at 1
+		for new_idx, old_idx in enumerate(optimized_order, 1):
+			new_idx = start + new_idx
+			old_idx = start + old_idx
+
+			self.delivery_stops[old_idx].idx = new_idx
+			stops_order.append(self.delivery_stops[old_idx])
+
+		self.delivery_stops[start:start + len(stops_order)] = stops_order
+
+
+@frappe.whitelist()
+def get_contact_and_address(name):
+	out = frappe._dict()
+
+	get_default_contact(out, name)
+	get_default_address(out, name)
+
+	return out
 
 
 def get_default_contact(out, name):
 	contact_persons = frappe.db.sql(
 		"""
-			select parent,
-				(select is_primary_contact from tabContact c where c.name = dl.parent)
-				as is_primary_contact
-			from
+			SELECT parent,
+				(SELECT is_primary_contact FROM tabContact c WHERE c.name = dl.parent) AS is_primary_contact
+			FROM
 				`tabDynamic Link` dl
-			where
-				dl.link_doctype="Customer" and
-				dl.link_name=%s and
-				dl.parenttype = 'Contact'
+			WHERE
+				dl.link_doctype="Customer"
+				AND dl.link_name=%s
+				AND dl.parenttype = "Contact"
 		""", (name), as_dict=1)
 
 	if contact_persons:
 		for out.contact_person in contact_persons:
 			if out.contact_person.is_primary_contact:
 				return out.contact_person
+
 		out.contact_person = contact_persons[0]
+
 		return out.contact_person
-	else:
-		return None
+
 
 def get_default_address(out, name):
 	shipping_addresses = frappe.db.sql(
 		"""
-			select parent,
-				(select is_shipping_address from tabAddress a where a.name=dl.parent) as is_shipping_address
-			from `tabDynamic Link` dl
-			where link_doctype="Customer"
-				and link_name=%s
-				and parenttype = 'Address'
+			SELECT parent,
+				(SELECT is_shipping_address FROM tabAddress a WHERE a.name=dl.parent) AS is_shipping_address
+			FROM
+				`tabDynamic Link` dl
+			WHERE
+				dl.link_doctype="Customer"
+				AND dl.link_name=%s
+				AND dl.parenttype = "Address"
 		""", (name), as_dict=1)
 
 	if shipping_addresses:
 		for out.shipping_address in shipping_addresses:
 			if out.shipping_address.is_shipping_address:
 				return out.shipping_address
+
 		out.shipping_address = shipping_addresses[0]
+
 		return out.shipping_address
-	else:
-		return None
-
-
-@frappe.whitelist()
-def get_contact_and_address(name):
-	out = frappe._dict()
-	get_default_contact(out, name)
-	get_default_address(out, name)
-	return out
 
 
 @frappe.whitelist()
@@ -103,67 +246,83 @@
 	contact_info = frappe.db.get_value(
 		"Contact", contact,
 		["first_name", "last_name", "phone", "mobile_no"],
-	as_dict=1)
+		as_dict=1)
+
 	contact_info.html = """ <b>%(first_name)s %(last_name)s</b> <br> %(phone)s <br> %(mobile_no)s""" % {
 		"first_name": contact_info.first_name,
 		"last_name": contact_info.last_name or "",
 		"phone": contact_info.phone or "",
-		"mobile_no": contact_info.mobile_no or "",
+		"mobile_no": contact_info.mobile_no or ""
 	}
+
 	return contact_info.html
 
 
-def process_route(name, optimize):
-	doc = frappe.get_doc("Delivery Trip", name)
-	settings = frappe.get_single("Google Maps Settings")
-	gmaps_client = settings.get_client()
+@frappe.whitelist()
+def optimize_route(delivery_trip):
+	delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
+	delivery_trip.process_route(optimize=True)
 
-	if not settings.enabled:
-		frappe.throw(_("Google Maps integration is not enabled"))
 
-	home_address = get_address_display(frappe.get_doc("Address", settings.home_address).as_dict())
-	address_list = []
+@frappe.whitelist()
+def get_arrival_times(delivery_trip):
+	delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
+	delivery_trip.process_route(optimize=False)
 
-	for stop in doc.delivery_stops:
-		address_list.append(stop.customer_address)
 
-	# Cannot add datetime.date to datetime.timedelta
-	departure_datetime = get_datetime(doc.date) + doc.departure_time
+def sanitize_address(address):
+	"""
+	Remove HTML breaks in a given address
 
-	try:
-		directions = gmaps_client.directions(origin=home_address,
-					destination=home_address, waypoints=address_list,
-					optimize_waypoints=optimize, departure_time=departure_datetime)
-	except Exception as e:
-		frappe.throw((e.message))
+	Args:
+		address (str): Address to be sanitized
 
-	if not directions:
+	Returns:
+		(str): Sanitized address
+	"""
+
+	if not address:
 		return
 
-	directions = directions[0]
-	duration = 0
+	address = address.split('<br>')
 
-	# Google Maps returns the optimized order of the waypoints that were sent
-	for idx, order in enumerate(directions.get("waypoint_order")):
-		# We accordingly rearrange the rows
-		doc.delivery_stops[order].idx = idx + 1
-		# Google Maps returns the "legs" in the optimized order, so we loop through it
-		duration += directions.get("legs")[idx].get("duration").get("value")
-		arrival_datetime = departure_datetime + datetime.timedelta(seconds=duration)
-		doc.delivery_stops[order].estimated_arrival = arrival_datetime
-
-	doc.save()
-	frappe.db.commit()
+	# Only get the first 3 blocks of the address
+	return ', '.join(address[:3])
 
 
-@frappe.whitelist()
-def optimize_route(name):
-	process_route(name, optimize=True)
+def get_directions(route, optimize):
+	"""
+	Retrieve map directions for a given route and departure time.
+	If optimize is `True`, Google Maps will return an optimized
+	order for the intermediate waypoints.
 
+	NOTE: Google's API does take an additional `departure_time` key,
+	but it only works for routes without any waypoints.
 
-@frappe.whitelist()
-def get_arrival_times(name):
-	process_route(name, optimize=False)
+	Args:
+		route (list of str): Route addresses (origin -> waypoint(s), if any -> destination)
+		optimize (bool): `True` if route needs to be optimized, else `False`
+
+	Returns:
+		(dict): Route legs and, if `optimize` is `True`, optimized waypoint order
+	"""
+
+	settings = frappe.get_single("Google Maps Settings")
+	maps_client = settings.get_client()
+
+	directions_data = {
+		"origin": route[0],
+		"destination": route[-1],
+		"waypoints": route[1: -1],
+		"optimize_waypoints": optimize
+	}
+
+	try:
+		directions = maps_client.directions(**directions_data)
+	except Exception as e:
+		frappe.throw(_(e.message))
+
+	return directions[0] if directions else False
 
 
 @frappe.whitelist()
@@ -171,10 +330,6 @@
 	delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
 
 	context = delivery_trip.as_dict()
-	context.update({
-		"departure_time": cstr(context.get("departure_time")),
-		"estimated_arrival": cstr(context.get("estimated_arrival"))
-	})
 
 	if delivery_trip.driver:
 		context.update(frappe.db.get_value("Driver", delivery_trip.driver, "cell_number", as_dict=1))
diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
index fae03f8..b0a3d31 100644
--- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
@@ -9,7 +9,7 @@
 import frappe
 from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers
 from erpnext.tests.utils import create_test_contact_and_address
-from frappe.utils import add_days, now_datetime, nowdate
+from frappe.utils import add_days, now_datetime
 
 
 class TestDeliveryTrip(unittest.TestCase):
@@ -19,28 +19,58 @@
 		create_delivery_notification()
 		create_test_contact_and_address()
 
-	def test_delivery_trip(self):
-		contact = get_contact_and_address("_Test Customer")
+		settings = frappe.get_single("Google Maps Settings")
+		settings.home_address = frappe.get_last_doc("Address").name
+		settings.save()
 
-		if not frappe.db.exists("Delivery Trip", "TOUR-00000"):
-			delivery_trip = frappe.get_doc({
-				"doctype": "Delivery Trip",
-				"company": erpnext.get_default_company(),
-				"date": add_days(nowdate(), 5),
-				"departure_time": add_days(now_datetime(), 5),
-				"driver": frappe.db.get_value('Driver', {"full_name": "Newton Scmander"}),
-				"vehicle": "JB 007",
-				"delivery_stops": [{
-					"customer": "_Test Customer",
-					"address": contact.shipping_address.parent,
-					"contact": contact.contact_person.parent
-				}]
-			})
-			delivery_trip.insert()
+		self.delivery_trip = create_delivery_trip()
 
-			notify_customers(delivery_trip=delivery_trip.name)
-			delivery_trip.load_from_db()
-			self.assertEqual(delivery_trip.email_notification_sent, 1)
+	def tearDown(self):
+		frappe.db.sql("delete from `tabDriver`")
+		frappe.db.sql("delete from `tabVehicle`")
+		frappe.db.sql("delete from `tabEmail Template`")
+		frappe.db.sql("delete from `tabDelivery Trip`")
+
+	def test_delivery_trip_notify_customers(self):
+		notify_customers(delivery_trip=self.delivery_trip.name)
+		self.delivery_trip.load_from_db()
+		self.assertEqual(self.delivery_trip.email_notification_sent, 1)
+
+	def test_unoptimized_route_list_without_locks(self):
+		route_list = self.delivery_trip.form_route_list(optimize=False)
+
+		# Return a single list of destinations, from home address and back
+		self.assertEqual(len(route_list), 1)
+		self.assertEqual(len(route_list[0]), 4)
+
+	def test_unoptimized_route_list_with_locks(self):
+		self.delivery_trip.delivery_stops[0].lock = 1
+		self.delivery_trip.save()
+		route_list = self.delivery_trip.form_route_list(optimize=False)
+
+		# Return a single list of destinations, from home address and back,
+		# since the stops don't need to optimized and simple time
+		# estimation is enough
+		self.assertEqual(len(route_list), 1)
+		self.assertEqual(len(route_list[0]), 4)
+
+	def test_optimized_route_list_without_locks(self):
+		route_list = self.delivery_trip.form_route_list(optimize=True)
+
+		# Return a single list of destinations, from home address and back,
+		# since the route doesn't have any locks to be optimized against
+		self.assertEqual(len(route_list), 1)
+		self.assertEqual(len(route_list[0]), 4)
+
+	def test_optimized_route_list_with_locks(self):
+		self.delivery_trip.delivery_stops[0].lock = 1
+		self.delivery_trip.save()
+		route_list = self.delivery_trip.form_route_list(optimize=True)
+
+		# Return multiple route lists, taking the home address as start and end
+		self.assertEqual(len(route_list), 2)
+		self.assertEqual(len(route_list[0]), 2)  # [home_address, locked_stop]
+		self.assertEqual(len(route_list[1]), 3)  # [locked_stop, second_stop, home_address]
 
 
 def create_driver():
@@ -67,6 +97,7 @@
 
 	delivery_settings = frappe.get_single("Delivery Settings")
 	delivery_settings.dispatch_template = 'Delivery Notification'
+	delivery_settings.save()
 
 
 def create_vehicle():
@@ -84,3 +115,30 @@
 			"vehicle_value": frappe.utils.flt(500000)
 		})
 		vehicle.insert()
+
+
+def create_delivery_trip(contact=None):
+	if not contact:
+		contact = get_contact_and_address("_Test Customer")
+
+	delivery_trip = frappe.new_doc("Delivery Trip")
+	delivery_trip.update({
+		"doctype": "Delivery Trip",
+		"company": erpnext.get_default_company(),
+		"departure_time": add_days(now_datetime(), 5),
+		"driver": frappe.db.get_value('Driver', {"full_name": "Newton Scmander"}),
+		"vehicle": "JB 007",
+		"delivery_stops": [{
+			"customer": "_Test Customer",
+			"address": contact.shipping_address.parent,
+			"contact": contact.contact_person.parent
+		},
+		{
+			"customer": "_Test Customer",
+			"address": contact.shipping_address.parent,
+			"contact": contact.contact_person.parent
+		}]
+	})
+	delivery_trip.insert()
+
+	return delivery_trip