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 erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '10.1.68'
|
__version__ = '10.1.70'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''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
|
return paid_amount[0][0] if paid_amount else 0
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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({
|
return frappe._dict({
|
||||||
"party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
|
"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),
|
"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
|
"discount_percentage": 0.0
|
||||||
})
|
})
|
||||||
else:
|
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'):
|
elif args.get('pricing_rule'):
|
||||||
item_details = remove_pricing_rule_for_item(args.get("pricing_rule"), item_details)
|
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.selling = 1 if doctype == "Customer" else 0
|
||||||
doc.buying = 1 if doctype == "Supplier" 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):
|
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)
|
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||||
|
|
||||||
if not self.grand_total:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_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')))
|
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
|
||||||
if filters.get('party_type') == 'Student':
|
if filters.get('party_type') == 'Student':
|
||||||
party_name_field = 'first_name'
|
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 {}
|
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
|
||||||
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
|
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"):
|
if self.get("group_same_items"):
|
||||||
self.group_similar_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):
|
def validate_paid_amount(self):
|
||||||
if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
|
if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
|
||||||
is_paid = self.get("is_pos") or self.get("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)]))
|
sales_orders = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)]))
|
||||||
if sales_orders:
|
if sales_orders:
|
||||||
po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', 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):
|
def validate_items(self):
|
||||||
# validate items to see if they have is_sales_item enabled
|
# 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)"
|
app_license = "GNU General Public License (v3)"
|
||||||
source_link = "https://github.com/frappe/erpnext"
|
source_link = "https://github.com/frappe/erpnext"
|
||||||
|
|
||||||
develop_version = '11.x.x-develop'
|
develop_version = '12.x.x-develop'
|
||||||
staging_version = '11.0.3-beta.19'
|
staging_version = '11.0.3-beta.20'
|
||||||
|
|
||||||
error_report_email = "support@erpnext.com"
|
error_report_email = "support@erpnext.com"
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"depends_on": "eval:!doc.__islocal",
|
"depends_on": "",
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -1976,7 +1976,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-10-11 11:52:39.047935",
|
"modified": "2018-10-24 02:07:21.618275",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"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
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
$.extend(cur_frm.cscript, {
|
$.extend(cur_frm.cscript, {
|
||||||
validate: function(doc, cdt, cdn) {
|
onload: function (doc, cdt, cdn) {
|
||||||
return $c_obj(doc, 'get_defaults', '', function(r, rt){
|
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;
|
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_copy": 1,
|
||||||
"allow_email": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_print": 0,
|
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
"allow_trash": 0,
|
"beta": 0,
|
||||||
"creation": "2013-05-02 17:53:24",
|
"creation": "2013-05-02 17:53:24",
|
||||||
"custom": 0,
|
"custom": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 0,
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"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,
|
||||||
"fieldname": "default_company",
|
"fieldname": "default_company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Default Company",
|
"label": "Default Company",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
"fieldname": "current_fiscal_year",
|
"fieldname": "current_fiscal_year",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Current Fiscal Year",
|
"label": "Current Fiscal Year",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
"fieldname": "country",
|
"fieldname": "country",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Country",
|
"label": "Country",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"options": "Country",
|
"options": "Country",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
|
"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",
|
"fieldname": "column_break_8",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
"default": "INR",
|
"default": "INR",
|
||||||
"fieldname": "default_currency",
|
"fieldname": "default_currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -124,26 +185,32 @@
|
|||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Default Currency",
|
"label": "Default Currency",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"options": "Currency",
|
"options": "Currency",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
"description": "Do not show any symbol like $ etc next to currencies.",
|
"description": "Do not show any symbol like $ etc next to currencies.",
|
||||||
"fieldname": "hide_currency_symbol",
|
"fieldname": "hide_currency_symbol",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@ -151,26 +218,32 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Hide Currency Symbol",
|
"label": "Hide Currency Symbol",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"options": "\nNo\nYes",
|
"options": "\nNo\nYes",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
"description": "If disable, 'Rounded Total' field will not be visible in any transaction",
|
"description": "If disable, 'Rounded Total' field will not be visible in any transaction",
|
||||||
"fieldname": "disable_rounded_total",
|
"fieldname": "disable_rounded_total",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@ -178,25 +251,31 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Disable Rounded Total",
|
"label": "Disable Rounded Total",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_column": 0,
|
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 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,
|
||||||
"description": "If disable, 'In Words' field will not be visible in any transaction",
|
"description": "If disable, 'In Words' field will not be visible in any transaction",
|
||||||
"fieldname": "disable_in_words",
|
"fieldname": "disable_in_words",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@ -204,7 +283,9 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Disable In Words",
|
"label": "Disable In Words",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -213,26 +294,28 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"image_view": 0,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
|
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"is_transaction_doc": 0,
|
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2016-03-03 16:14:41.260467",
|
"modified": "2018-10-15 03:08:19.886212",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Global Defaults",
|
"name": "Global Defaults",
|
||||||
@ -240,7 +323,6 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 0,
|
"delete": 0,
|
||||||
@ -252,7 +334,6 @@
|
|||||||
"print": 0,
|
"print": 0,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
"report": 0,
|
||||||
"restrict": 0,
|
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
"set_user_permissions": 0,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
@ -260,10 +341,12 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"quick_entry": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
"show_in_menu": 0,
|
"show_name_in_global_search": 0,
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"use_template": 0,
|
"track_changes": 0,
|
||||||
"version": 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,
|
"set_only_once": 0,
|
||||||
"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": "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,
|
"has_web_view": 0,
|
||||||
@ -155,7 +219,7 @@
|
|||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-09-05 00:16:23.569855",
|
"modified": "2018-09-09 23:51:34.279941",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Settings",
|
"name": "Delivery Settings",
|
||||||
|
@ -493,6 +493,38 @@
|
|||||||
"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": "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_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@ -525,6 +557,168 @@
|
|||||||
"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": "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_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@ -568,7 +762,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-09-05 00:51:55.275009",
|
"modified": "2018-10-11 22:32:27.450906",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Stop",
|
"name": "Delivery Stop",
|
||||||
|
@ -65,31 +65,43 @@ frappe.ui.form.on('Delivery Trip', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
calculate_arrival_time: function (frm) {
|
calculate_arrival_time: function (frm) {
|
||||||
frappe.call({
|
frappe.db.get_value("Google Maps Settings", { name: "Google Maps Settings" }, "enabled", (r) => {
|
||||||
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.get_arrival_times',
|
if (r.enabled == 0) {
|
||||||
freeze: true,
|
frappe.throw(__("Please enable Google Maps Settings to estimate and optimize routes"));
|
||||||
freeze_message: __("Updating estimated arrival times."),
|
} else {
|
||||||
args: {
|
frappe.call({
|
||||||
name: frm.doc.name,
|
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.get_arrival_times',
|
||||||
},
|
freeze: true,
|
||||||
callback: function (r) {
|
freeze_message: __("Updating estimated arrival times."),
|
||||||
frm.reload_doc();
|
args: {
|
||||||
|
delivery_trip: frm.doc.name,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
optimize_route: function (frm) {
|
optimize_route: function (frm) {
|
||||||
frappe.call({
|
frappe.db.get_value("Google Maps Settings", {name: "Google Maps Settings"}, "enabled", (r) => {
|
||||||
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.optimize_route',
|
if (r.enabled == 0) {
|
||||||
freeze: true,
|
frappe.throw(__("Please enable Google Maps Settings to estimate and optimize routes"));
|
||||||
freeze_message: __("Optimizing routes."),
|
} else {
|
||||||
args: {
|
frappe.call({
|
||||||
name: frm.doc.name,
|
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.optimize_route',
|
||||||
},
|
freeze: true,
|
||||||
callback: function (r) {
|
freeze_message: __("Optimizing routes."),
|
||||||
frm.reload_doc();
|
args: {
|
||||||
|
delivery_trip: frm.doc.name,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
notify_customers: function (frm) {
|
notify_customers: function (frm) {
|
||||||
|
@ -159,101 +159,7 @@
|
|||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"length": 0,
|
"label": "Delivery Details",
|
||||||
"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,
|
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
@ -336,6 +242,104 @@
|
|||||||
"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": "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_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@ -369,6 +373,38 @@
|
|||||||
"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": "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_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@ -441,7 +477,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"depends_on": "eval:!cur_frm.is_new()",
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "calculate_arrival_time",
|
"fieldname": "calculate_arrival_time",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -474,6 +510,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "optimize_route",
|
"fieldname": "optimize_route",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -574,7 +611,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-09-05 01:20:34.165834",
|
"modified": "2018-10-11 22:32:04.355068",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Trip",
|
"name": "Delivery Trip",
|
||||||
@ -627,5 +664,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 0,
|
"track_changes": 0,
|
||||||
"track_seen": 0
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
@ -10,17 +10,43 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.contacts.doctype.address.address import get_address_display
|
from frappe.contacts.doctype.address.address import get_address_display
|
||||||
from frappe.model.document import Document
|
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):
|
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):
|
def on_submit(self):
|
||||||
self.update_delivery_notes()
|
self.update_delivery_notes()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_delivery_notes(delete=True)
|
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):
|
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]))
|
delivery_notes = list(set([stop.delivery_note for stop in self.delivery_stops if stop.delivery_note]))
|
||||||
|
|
||||||
update_fields = {
|
update_fields = {
|
||||||
@ -28,7 +54,7 @@ class DeliveryTrip(Document):
|
|||||||
"driver_name": self.driver_name,
|
"driver_name": self.driver_name,
|
||||||
"vehicle_no": self.vehicle,
|
"vehicle_no": self.vehicle,
|
||||||
"lr_no": self.name,
|
"lr_no": self.name,
|
||||||
"lr_date": self.date
|
"lr_date": self.departure_time
|
||||||
}
|
}
|
||||||
|
|
||||||
for delivery_note in delivery_notes:
|
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]
|
delivery_notes = [get_link_to_form("Delivery Note", note) for note in delivery_notes]
|
||||||
frappe.msgprint(_("Delivery Notes {0} updated".format(", ".join(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):
|
def get_default_contact(out, name):
|
||||||
contact_persons = frappe.db.sql(
|
contact_persons = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select parent,
|
SELECT parent,
|
||||||
(select is_primary_contact from tabContact c where c.name = dl.parent)
|
(SELECT is_primary_contact FROM tabContact c WHERE c.name = dl.parent) AS is_primary_contact
|
||||||
as is_primary_contact
|
FROM
|
||||||
from
|
|
||||||
`tabDynamic Link` dl
|
`tabDynamic Link` dl
|
||||||
where
|
WHERE
|
||||||
dl.link_doctype="Customer" and
|
dl.link_doctype="Customer"
|
||||||
dl.link_name=%s and
|
AND dl.link_name=%s
|
||||||
dl.parenttype = 'Contact'
|
AND dl.parenttype = "Contact"
|
||||||
""", (name), as_dict=1)
|
""", (name), as_dict=1)
|
||||||
|
|
||||||
if contact_persons:
|
if contact_persons:
|
||||||
for out.contact_person in contact_persons:
|
for out.contact_person in contact_persons:
|
||||||
if out.contact_person.is_primary_contact:
|
if out.contact_person.is_primary_contact:
|
||||||
return out.contact_person
|
return out.contact_person
|
||||||
|
|
||||||
out.contact_person = contact_persons[0]
|
out.contact_person = contact_persons[0]
|
||||||
|
|
||||||
return out.contact_person
|
return out.contact_person
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_default_address(out, name):
|
def get_default_address(out, name):
|
||||||
shipping_addresses = frappe.db.sql(
|
shipping_addresses = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select parent,
|
SELECT parent,
|
||||||
(select is_shipping_address from tabAddress a where a.name=dl.parent) as is_shipping_address
|
(SELECT is_shipping_address FROM tabAddress a WHERE a.name=dl.parent) AS is_shipping_address
|
||||||
from `tabDynamic Link` dl
|
FROM
|
||||||
where link_doctype="Customer"
|
`tabDynamic Link` dl
|
||||||
and link_name=%s
|
WHERE
|
||||||
and parenttype = 'Address'
|
dl.link_doctype="Customer"
|
||||||
|
AND dl.link_name=%s
|
||||||
|
AND dl.parenttype = "Address"
|
||||||
""", (name), as_dict=1)
|
""", (name), as_dict=1)
|
||||||
|
|
||||||
if shipping_addresses:
|
if shipping_addresses:
|
||||||
for out.shipping_address in shipping_addresses:
|
for out.shipping_address in shipping_addresses:
|
||||||
if out.shipping_address.is_shipping_address:
|
if out.shipping_address.is_shipping_address:
|
||||||
return out.shipping_address
|
return out.shipping_address
|
||||||
|
|
||||||
out.shipping_address = shipping_addresses[0]
|
out.shipping_address = shipping_addresses[0]
|
||||||
|
|
||||||
return out.shipping_address
|
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()
|
@frappe.whitelist()
|
||||||
@ -103,67 +246,83 @@ def get_contact_display(contact):
|
|||||||
contact_info = frappe.db.get_value(
|
contact_info = frappe.db.get_value(
|
||||||
"Contact", contact,
|
"Contact", contact,
|
||||||
["first_name", "last_name", "phone", "mobile_no"],
|
["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""" % {
|
contact_info.html = """ <b>%(first_name)s %(last_name)s</b> <br> %(phone)s <br> %(mobile_no)s""" % {
|
||||||
"first_name": contact_info.first_name,
|
"first_name": contact_info.first_name,
|
||||||
"last_name": contact_info.last_name or "",
|
"last_name": contact_info.last_name or "",
|
||||||
"phone": contact_info.phone or "",
|
"phone": contact_info.phone or "",
|
||||||
"mobile_no": contact_info.mobile_no or "",
|
"mobile_no": contact_info.mobile_no or ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return contact_info.html
|
return contact_info.html
|
||||||
|
|
||||||
|
|
||||||
def process_route(name, optimize):
|
@frappe.whitelist()
|
||||||
doc = frappe.get_doc("Delivery Trip", name)
|
def optimize_route(delivery_trip):
|
||||||
settings = frappe.get_single("Google Maps Settings")
|
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
|
||||||
gmaps_client = settings.get_client()
|
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())
|
@frappe.whitelist()
|
||||||
address_list = []
|
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
|
def sanitize_address(address):
|
||||||
departure_datetime = get_datetime(doc.date) + doc.departure_time
|
"""
|
||||||
|
Remove HTML breaks in a given address
|
||||||
|
|
||||||
try:
|
Args:
|
||||||
directions = gmaps_client.directions(origin=home_address,
|
address (str): Address to be sanitized
|
||||||
destination=home_address, waypoints=address_list,
|
|
||||||
optimize_waypoints=optimize, departure_time=departure_datetime)
|
|
||||||
except Exception as e:
|
|
||||||
frappe.throw((e.message))
|
|
||||||
|
|
||||||
if not directions:
|
Returns:
|
||||||
|
(str): Sanitized address
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not address:
|
||||||
return
|
return
|
||||||
|
|
||||||
directions = directions[0]
|
address = address.split('<br>')
|
||||||
duration = 0
|
|
||||||
|
|
||||||
# Google Maps returns the optimized order of the waypoints that were sent
|
# Only get the first 3 blocks of the address
|
||||||
for idx, order in enumerate(directions.get("waypoint_order")):
|
return ', '.join(address[:3])
|
||||||
# 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()
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
def get_directions(route, optimize):
|
||||||
def optimize_route(name):
|
"""
|
||||||
process_route(name, optimize=True)
|
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()
|
Args:
|
||||||
def get_arrival_times(name):
|
route (list of str): Route addresses (origin -> waypoint(s), if any -> destination)
|
||||||
process_route(name, optimize=False)
|
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()
|
@frappe.whitelist()
|
||||||
@ -171,10 +330,6 @@ def notify_customers(delivery_trip):
|
|||||||
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
|
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
|
||||||
|
|
||||||
context = delivery_trip.as_dict()
|
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:
|
if delivery_trip.driver:
|
||||||
context.update(frappe.db.get_value("Driver", delivery_trip.driver, "cell_number", as_dict=1))
|
context.update(frappe.db.get_value("Driver", delivery_trip.driver, "cell_number", as_dict=1))
|
||||||
|
@ -9,7 +9,7 @@ import erpnext
|
|||||||
import frappe
|
import frappe
|
||||||
from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers
|
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 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):
|
class TestDeliveryTrip(unittest.TestCase):
|
||||||
@ -19,28 +19,58 @@ class TestDeliveryTrip(unittest.TestCase):
|
|||||||
create_delivery_notification()
|
create_delivery_notification()
|
||||||
create_test_contact_and_address()
|
create_test_contact_and_address()
|
||||||
|
|
||||||
def test_delivery_trip(self):
|
settings = frappe.get_single("Google Maps Settings")
|
||||||
contact = get_contact_and_address("_Test Customer")
|
settings.home_address = frappe.get_last_doc("Address").name
|
||||||
|
settings.save()
|
||||||
|
|
||||||
if not frappe.db.exists("Delivery Trip", "TOUR-00000"):
|
self.delivery_trip = create_delivery_trip()
|
||||||
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()
|
|
||||||
|
|
||||||
notify_customers(delivery_trip=delivery_trip.name)
|
def tearDown(self):
|
||||||
delivery_trip.load_from_db()
|
frappe.db.sql("delete from `tabDriver`")
|
||||||
self.assertEqual(delivery_trip.email_notification_sent, 1)
|
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():
|
def create_driver():
|
||||||
@ -67,6 +97,7 @@ def create_delivery_notification():
|
|||||||
|
|
||||||
delivery_settings = frappe.get_single("Delivery Settings")
|
delivery_settings = frappe.get_single("Delivery Settings")
|
||||||
delivery_settings.dispatch_template = 'Delivery Notification'
|
delivery_settings.dispatch_template = 'Delivery Notification'
|
||||||
|
delivery_settings.save()
|
||||||
|
|
||||||
|
|
||||||
def create_vehicle():
|
def create_vehicle():
|
||||||
@ -84,3 +115,30 @@ def create_vehicle():
|
|||||||
"vehicle_value": frappe.utils.flt(500000)
|
"vehicle_value": frappe.utils.flt(500000)
|
||||||
})
|
})
|
||||||
vehicle.insert()
|
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