Added feature to optimize routes, also cleaned up code (#15193)

diff --git a/erpnext/stock/doctype/delivery_stop/delivery_stop.json b/erpnext/stock/doctype/delivery_stop/delivery_stop.json
index cd71f21..4fe4102 100644
--- a/erpnext/stock/doctype/delivery_stop/delivery_stop.json
+++ b/erpnext/stock/doctype/delivery_stop/delivery_stop.json
@@ -14,6 +14,7 @@
  "fields": [
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -46,6 +47,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -78,6 +80,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -108,6 +111,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -139,6 +143,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -169,6 +174,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -201,6 +207,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -231,6 +238,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -262,6 +270,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -292,12 +301,13 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
    "fieldname": "estimated_arrival", 
-   "fieldtype": "Time", 
+   "fieldtype": "Datetime", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
@@ -323,6 +333,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -354,6 +365,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -384,6 +396,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -416,6 +429,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -446,6 +460,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 1, 
    "bold": 0, 
    "collapsible": 0, 
@@ -477,6 +492,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -507,6 +523,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -547,7 +564,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-02-22 16:43:55.257470", 
+ "modified": "2018-08-21 22:25:53.276548", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Delivery Stop", 
@@ -561,5 +578,6 @@
  "sort_field": "modified", 
  "sort_order": "DESC", 
  "track_changes": 1, 
- "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.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
old mode 100644
new mode 100755
index 3e7dfc9..f3c0f75
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
@@ -66,17 +66,27 @@
 
 	calculate_arrival_time: function (frm) {
 		frappe.call({
-			method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.calculate_time_matrix',
+			method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.get_arrival_times',
 			freeze: true,
 			freeze_message: __("Updating estimated arrival times."),
 			args: {
-				name: frm.doc.name
+				name: frm.doc.name,
 			},
 			callback: function (r) {
-				if (r.message.error) {
-					frappe.throw(__("Malformatted address for {0}, please fix to continue.",
-						[r.message.error.destination.address]));
-				}
+				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();
 			}
 		});
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
index 0b09e83..36f71a7 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
@@ -474,6 +474,39 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "optimize_route", 
+   "fieldtype": "Button", 
+   "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": "Optimize Route", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "", 
+   "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": "section_break_13", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -575,7 +608,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-08-21 14:44:36.993178", 
+ "modified": "2018-08-29 14:44:36.993178", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Delivery Trip", 
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 955783a..9e55f8c 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -5,15 +5,16 @@
 from __future__ import unicode_literals
 import datetime
 import frappe
-import googlemaps
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils.user import get_user_fullname
-from frappe.utils import getdate, cstr
+from frappe.utils import getdate, cstr, get_datetime
+from frappe.contacts.doctype.address.address import get_address_display
 
 class DeliveryTrip(Document):
 	pass
 
+
 def get_default_contact(out, name):
 	contact_persons = frappe.db.sql(
 		"""
@@ -80,61 +81,59 @@
 	}
 	return contact_info.html
 
-@frappe.whitelist()
-def calculate_time_matrix(name):
-	"""Calucation and round in closest 15 minutes, delivery stops"""
 
-	gmaps = frappe.db.get_value('Google Maps', None,
-		['client_key', 'enabled', 'home_address'], as_dict=1)
+def process_route(name, optimize):
+	doc = frappe.get_doc("Delivery Trip", name)
+	settings = frappe.get_single("Google Maps Settings")
+	gmaps_client = settings.get_client()
 
-	if not gmaps.enabled:
+	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 = []
+
+	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
+
 	try:
-		gmaps_client = googlemaps.Client(key=gmaps.client_key)
+		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)
+		frappe.throw((e.message))
 
-	secs_15min = 900
-	doc = frappe.get_doc('Delivery Trip', name)
-	departure_time = doc.departure_time
-	matrix_duration = []
+	if not directions:
+		return
 
-	for i, stop in enumerate(doc.delivery_stops):
-		if i == 0:
-			# The first row is the starting pointing
-			origin = gmaps.home_address
-			destination = format_address(doc.delivery_stops[i].address)
-			distance_calc = gmaps_client.distance_matrix(origin, destination)
-			matrix_duration.append(distance_calc)
+	directions = directions[0]
+	duration = 0
 
-			try:
-				distance_secs = distance_calc['rows'][0]['elements'][0]['duration']['value']
-			except Exception as e:
-				frappe.throw(_("Error '{0}' occured. Arguments {1}.").format(e.message, e.args))
+	# 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
 
-			stop.estimated_arrival = round_timedelta(
-				departure_time + datetime.timedelta(0, distance_secs + secs_15min),
-				datetime.timedelta(minutes=15))
-		else:
-			# Calculation based on previous
-			origin = format_address(doc.delivery_stops[i - 1].address)
-			destination = format_address(doc.delivery_stops[i].address)
-			distance_calc = gmaps_client.distance_matrix(origin, destination)
-			matrix_duration.append(distance_calc)
+	doc.save()
+	frappe.db.commit()
 
-			try:
-				distance_secs = distance_calc['rows'][0]['elements'][0]['duration']['value']
-			except Exception as e:
-				frappe.throw(_("Error '{0}' occured. Arguments {1}.").format(e.message, e.args))
 
-			stop.estimated_arrival = round_timedelta(
-				doc.delivery_stops[i - 1].estimated_arrival +
-				datetime.timedelta(0, distance_secs + secs_15min), datetime.timedelta(minutes=15))
-		stop.save()
-		frappe.db.commit()
+@frappe.whitelist()
+def optimize_route(name):
+	process_route(name, optimize=True)
 
-	return matrix_duration
+
+@frappe.whitelist()
+def get_arrival_times(name):
+	process_route(name, optimize=False)
+
 
 @frappe.whitelist()
 def notify_customers(docname, date, driver, vehicle, sender_email, delivery_notification):