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

This commit is contained in:
Valmik 2018-08-31 16:39:29 +05:30 committed by Nabin Hait
parent a2288ff8e7
commit 2175b90b8d
4 changed files with 114 additions and 54 deletions

View File

@ -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
}

22
erpnext/stock/doctype/delivery_trip/delivery_trip.js Normal file → Executable file
View File

@ -66,17 +66,27 @@ frappe.ui.form.on('Delivery Trip', {
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) {
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) {
if (r.message.error) {
frappe.throw(__("Malformatted address for {0}, please fix to continue.",
[r.message.error.destination.address]));
}
frm.reload_doc();
}
});

View File

@ -467,6 +467,39 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"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,
@ -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",

View File

@ -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 @@ def get_contact_display(contact):
}
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)
@frappe.whitelist()
def get_arrival_times(name):
process_route(name, optimize=False)
return matrix_duration
@frappe.whitelist()
def notify_customers(docname, date, driver, vehicle, sender_email, delivery_notification):