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):