mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-20 13:39:18 +00:00
Added feature to optimize routes, also cleaned up code (#15193)
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -78,6 +80,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -108,6 +111,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -139,6 +143,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -169,6 +174,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -201,6 +207,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -231,6 +238,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -262,6 +270,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -292,12 +301,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "estimated_arrival",
|
"fieldname": "estimated_arrival",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Datetime",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
@@ -323,6 +333,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -354,6 +365,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -384,6 +396,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -416,6 +429,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -446,6 +460,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -477,6 +492,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -507,6 +523,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -547,7 +564,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-02-22 16:43:55.257470",
|
"modified": "2018-08-21 22:25:53.276548",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Stop",
|
"name": "Delivery Stop",
|
||||||
@@ -561,5 +578,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"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
22
erpnext/stock/doctype/delivery_trip/delivery_trip.js
Normal file → Executable file
@@ -66,17 +66,27 @@ frappe.ui.form.on('Delivery Trip', {
|
|||||||
|
|
||||||
calculate_arrival_time: function (frm) {
|
calculate_arrival_time: function (frm) {
|
||||||
frappe.call({
|
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: true,
|
||||||
freeze_message: __("Updating estimated arrival times."),
|
freeze_message: __("Updating estimated arrival times."),
|
||||||
args: {
|
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) {
|
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();
|
frm.reload_doc();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -467,6 +467,39 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 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_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@@ -575,7 +608,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-08-21 14:44:36.993178",
|
"modified": "2018-08-29 14:44:36.993178",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Trip",
|
"name": "Delivery Trip",
|
||||||
|
|||||||
@@ -5,15 +5,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import datetime
|
import datetime
|
||||||
import frappe
|
import frappe
|
||||||
import googlemaps
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils.user import get_user_fullname
|
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):
|
class DeliveryTrip(Document):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_default_contact(out, name):
|
def get_default_contact(out, name):
|
||||||
contact_persons = frappe.db.sql(
|
contact_persons = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
@@ -80,61 +81,59 @@ def get_contact_display(contact):
|
|||||||
}
|
}
|
||||||
return contact_info.html
|
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,
|
def process_route(name, optimize):
|
||||||
['client_key', 'enabled', 'home_address'], as_dict=1)
|
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"))
|
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:
|
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:
|
except Exception as e:
|
||||||
frappe.throw(e.message)
|
frappe.throw((e.message))
|
||||||
|
|
||||||
secs_15min = 900
|
if not directions:
|
||||||
doc = frappe.get_doc('Delivery Trip', name)
|
return
|
||||||
departure_time = doc.departure_time
|
|
||||||
matrix_duration = []
|
|
||||||
|
|
||||||
for i, stop in enumerate(doc.delivery_stops):
|
directions = directions[0]
|
||||||
if i == 0:
|
duration = 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)
|
|
||||||
|
|
||||||
try:
|
# Google Maps returns the optimized order of the waypoints that were sent
|
||||||
distance_secs = distance_calc['rows'][0]['elements'][0]['duration']['value']
|
for idx, order in enumerate(directions.get("waypoint_order")):
|
||||||
except Exception as e:
|
# We accordingly rearrange the rows
|
||||||
frappe.throw(_("Error '{0}' occured. Arguments {1}.").format(e.message, e.args))
|
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(
|
doc.save()
|
||||||
departure_time + datetime.timedelta(0, distance_secs + secs_15min),
|
frappe.db.commit()
|
||||||
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)
|
|
||||||
|
|
||||||
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(
|
@frappe.whitelist()
|
||||||
doc.delivery_stops[i - 1].estimated_arrival +
|
def optimize_route(name):
|
||||||
datetime.timedelta(0, distance_secs + secs_15min), datetime.timedelta(minutes=15))
|
process_route(name, optimize=True)
|
||||||
stop.save()
|
|
||||||
frappe.db.commit()
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_arrival_times(name):
|
||||||
|
process_route(name, optimize=False)
|
||||||
|
|
||||||
return matrix_duration
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def notify_customers(docname, date, driver, vehicle, sender_email, delivery_notification):
|
def notify_customers(docname, date, driver, vehicle, sender_email, delivery_notification):
|
||||||
|
|||||||
Reference in New Issue
Block a user