Merge branch 'staging-fixes' into staging
This commit is contained in:
commit
35ec2469e9
@ -5,7 +5,7 @@ import frappe
|
||||
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'''
|
||||
|
@ -933,7 +933,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
|
||||
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),
|
||||
|
@ -201,8 +201,8 @@ def get_pricing_rule_for_item(args):
|
||||
"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 @@ def make_pricing_rule(doctype, docname):
|
||||
doc.selling = 1 if doctype == "Customer" else 0
|
||||
doc.buying = 1 if doctype == "Supplier" else 0
|
||||
|
||||
return doc
|
||||
return doc
|
||||
|
@ -662,9 +662,6 @@ class SalesInvoice(SellingController):
|
||||
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()
|
||||
|
||||
|
@ -21,6 +21,8 @@ def get_data(filters, show_party_name):
|
||||
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],
|
||||
|
@ -117,6 +117,13 @@ class AccountsController(TransactionBase):
|
||||
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")
|
||||
|
@ -345,7 +345,8 @@ class SellingController(StockController):
|
||||
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
|
||||
|
@ -11,8 +11,8 @@ app_email = "info@erpnext.com"
|
||||
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"
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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] } };
|
||||
})
|
||||
}
|
||||
});
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
||||
]);
|
||||
|
||||
});
|
@ -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
|
@ -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",
|
||||
|
@ -493,6 +493,38 @@
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"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,
|
||||
@ -525,6 +557,168 @@
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"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,
|
||||
@ -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",
|
||||
|
@ -65,31 +65,43 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
},
|
||||
|
||||
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) {
|
||||
|
@ -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,
|
||||
@ -336,6 +242,104 @@
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"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,
|
||||
@ -369,6 +373,38 @@
|
||||
"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": "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,
|
||||
@ -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
|
||||
}
|
@ -10,17 +10,43 @@ import frappe
|
||||
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 @@ class DeliveryTrip(Document):
|
||||
"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 @@ class DeliveryTrip(Document):
|
||||
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 @@ def get_contact_display(contact):
|
||||
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 @@ def notify_customers(delivery_trip):
|
||||
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))
|
||||
|
@ -9,7 +9,7 @@ import erpnext
|
||||
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 @@ class TestDeliveryTrip(unittest.TestCase):
|
||||
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 @@ def create_delivery_notification():
|
||||
|
||||
delivery_settings = frappe.get_single("Delivery Settings")
|
||||
delivery_settings.dispatch_template = 'Delivery Notification'
|
||||
delivery_settings.save()
|
||||
|
||||
|
||||
def create_vehicle():
|
||||
@ -84,3 +115,30 @@ def create_vehicle():
|
||||
"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
|
||||
|
Loading…
Reference in New Issue
Block a user