From e5b57ec965101a6183b3f8f2d74b2645cb1ecdbe Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:15:31 +0200 Subject: [PATCH 001/107] feat: Overdue Payments table --- .../doctype/overdue_payments/__init__.py | 0 .../overdue_payments/overdue_payments.json | 171 ++++++++++++++++++ .../overdue_payments/overdue_payments.py | 8 + 3 files changed, 179 insertions(+) create mode 100644 erpnext/accounts/doctype/overdue_payments/__init__.py create mode 100644 erpnext/accounts/doctype/overdue_payments/overdue_payments.json create mode 100644 erpnext/accounts/doctype/overdue_payments/overdue_payments.py diff --git a/erpnext/accounts/doctype/overdue_payments/__init__.py b/erpnext/accounts/doctype/overdue_payments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/overdue_payments/overdue_payments.json b/erpnext/accounts/doctype/overdue_payments/overdue_payments.json new file mode 100644 index 0000000000..57104c186c --- /dev/null +++ b/erpnext/accounts/doctype/overdue_payments/overdue_payments.json @@ -0,0 +1,171 @@ +{ + "actions": [], + "creation": "2021-09-15 18:34:27.172906", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sales_invoice", + "payment_schedule", + "payment_term", + "section_break_15", + "description", + "section_break_4", + "due_date", + "overdue_days", + "mode_of_payment", + "column_break_5", + "invoice_portion", + "section_break_9", + "payment_amount", + "outstanding", + "paid_amount", + "discounted_amount", + "column_break_3", + "base_payment_amount", + "interest_amount" + ], + "fields": [ + { + "columns": 2, + "fieldname": "payment_term", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Payment Term", + "options": "Payment Term", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "columns": 2, + "fetch_from": "payment_term.description", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "read_only": 1 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "columns": 2, + "fieldname": "due_date", + "fieldtype": "Date", + "label": "Due Date", + "read_only": 1 + }, + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "fieldname": "invoice_portion", + "fieldtype": "Percent", + "label": "Invoice Portion", + "read_only": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "columns": 2, + "fieldname": "payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount", + "options": "currency", + "read_only": 1 + }, + { + "fetch_from": "payment_amount", + "fieldname": "outstanding", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Outstanding", + "options": "currency", + "read_only": 1 + }, + { + "depends_on": "paid_amount", + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "options": "currency" + }, + { + "default": "0", + "depends_on": "discounted_amount", + "fieldname": "discounted_amount", + "fieldtype": "Currency", + "label": "Discounted Amount", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "sales_invoice", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Invoice", + "options": "Sales Invoice", + "reqd": 1 + }, + { + "fieldname": "payment_schedule", + "fieldtype": "Data", + "label": "Payment Schedule", + "read_only": 1 + }, + { + "fieldname": "overdue_days", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Overdue Days", + "read_only": 1 + }, + { + "fieldname": "interest_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Interest Amount", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-15 19:04:54.082880", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Overdue Payments", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/overdue_payments/overdue_payments.py b/erpnext/accounts/doctype/overdue_payments/overdue_payments.py new file mode 100644 index 0000000000..844f8ecdbd --- /dev/null +++ b/erpnext/accounts/doctype/overdue_payments/overdue_payments.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class OverduePayments(Document): + pass From e7705327f003858b99215210869dbc1c24eff0b2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:15:53 +0200 Subject: [PATCH 002/107] feat: filter invoices --- erpnext/accounts/doctype/dunning/dunning.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 9909c6c2ab..2f997ba02e 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -3,11 +3,12 @@ frappe.ui.form.on("Dunning", { setup: function (frm) { - frm.set_query("sales_invoice", () => { + frm.set_query("sales_invoice", "overdue_payments", () => { return { filters: { docstatus: 1, company: frm.doc.company, + customer: frm.doc.customer, outstanding_amount: [">", 0], status: "Overdue" }, From 487c6018bfe6514972f4788584f2d6c83b2ce2b8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:16:09 +0200 Subject: [PATCH 003/107] feat: restructure dunning doctype --- erpnext/accounts/doctype/dunning/dunning.json | 108 +++++++++--------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index 2a32b99f42..a0ddf04b6c 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -8,20 +8,19 @@ "field_order": [ "title", "naming_series", - "sales_invoice", "customer", "customer_name", - "outstanding_amount", "currency", "conversion_rate", "column_break_3", "company", "posting_date", "posting_time", - "due_date", - "overdue_days", + "status", "address_and_contact_section", + "customer_address", "address_display", + "contact_person", "contact_display", "contact_mobile", "contact_email", @@ -29,16 +28,17 @@ "company_address_display", "section_break_6", "dunning_type", - "dunning_fee", "column_break_8", "rate_of_interest", - "interest_amount", "section_break_12", - "dunning_amount", + "overdue_payments", + "section_break_28", + "column_break_17", + "total_interest", + "total_outstanding", + "dunning_fee", "grand_total", "income_account", - "column_break_17", - "status", "printing_setting_section", "language", "body_text", @@ -62,15 +62,6 @@ "label": "Series", "options": "DUNN-.MM.-.YY.-" }, - { - "fieldname": "sales_invoice", - "fieldtype": "Link", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Sales Invoice", - "options": "Sales Invoice", - "reqd": 1 - }, { "fetch_from": "sales_invoice.customer_name", "fieldname": "customer_name", @@ -79,13 +70,6 @@ "label": "Customer Name", "read_only": 1 }, - { - "fetch_from": "sales_invoice.outstanding_amount", - "fieldname": "outstanding_amount", - "fieldtype": "Currency", - "label": "Outstanding Amount", - "read_only": 1 - }, { "fieldname": "column_break_3", "fieldtype": "Column Break" @@ -94,13 +78,8 @@ "default": "Today", "fieldname": "posting_date", "fieldtype": "Date", - "label": "Date" - }, - { - "fieldname": "overdue_days", - "fieldtype": "Int", - "label": "Overdue Days", - "read_only": 1 + "label": "Date", + "reqd": 1 }, { "fieldname": "section_break_6", @@ -115,14 +94,6 @@ "options": "Dunning Type", "reqd": 1 }, - { - "default": "0", - "fieldname": "interest_amount", - "fieldtype": "Currency", - "label": "Interest Amount", - "precision": "2", - "read_only": 1 - }, { "fieldname": "column_break_8", "fieldtype": "Column Break" @@ -134,6 +105,7 @@ "fieldname": "dunning_fee", "fieldtype": "Currency", "label": "Dunning Fee", + "options": "currency", "precision": "2" }, { @@ -201,13 +173,6 @@ "fieldtype": "Text Editor", "label": "Closing Text" }, - { - "fetch_from": "sales_invoice.due_date", - "fieldname": "due_date", - "fieldtype": "Date", - "label": "Due Date", - "read_only": 1 - }, { "fieldname": "posting_time", "fieldtype": "Time", @@ -222,6 +187,7 @@ "label": "Rate of Interest (%) Yearly" }, { + "collapsible": 1, "fieldname": "address_and_contact_section", "fieldtype": "Section Break", "label": "Address and Contact" @@ -273,13 +239,14 @@ "fieldtype": "Link", "label": "Customer", "options": "Customer", - "read_only": 1 + "reqd": 1 }, { "default": "0", "fieldname": "grand_total", "fieldtype": "Currency", "label": "Grand Total", + "options": "currency", "precision": "2", "read_only": 1 }, @@ -292,13 +259,6 @@ "label": "Status", "options": "Draft\nResolved\nUnresolved\nCancelled" }, - { - "fieldname": "dunning_amount", - "fieldtype": "Currency", - "hidden": 1, - "label": "Dunning Amount", - "read_only": 1 - }, { "fieldname": "income_account", "fieldtype": "Link", @@ -312,6 +272,44 @@ "hidden": 1, "label": "Conversion Rate", "read_only": 1 + }, + { + "fieldname": "overdue_payments", + "fieldtype": "Table", + "label": "Overdue Payments", + "options": "Overdue Payments" + }, + { + "fieldname": "section_break_28", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "total_interest", + "fieldtype": "Currency", + "label": "Total Interest", + "options": "currency", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "total_outstanding", + "fieldtype": "Currency", + "label": "Total Outstanding", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address" + }, + { + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact" } ], "is_submittable": 1, From 86a8b0b30f6ac29fed1b3a635e4b5103e008f628 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:09:32 +0200 Subject: [PATCH 004/107] refactor: doctype naming Overdue Payments -> Overdue Payment --- erpnext/accounts/doctype/dunning/dunning.json | 2 +- .../doctype/{overdue_payments => overdue_payment}/__init__.py | 0 .../overdue_payment.json} | 2 +- .../overdue_payments.py => overdue_payment/overdue_payment.py} | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename erpnext/accounts/doctype/{overdue_payments => overdue_payment}/__init__.py (100%) rename erpnext/accounts/doctype/{overdue_payments/overdue_payments.json => overdue_payment/overdue_payment.json} (99%) rename erpnext/accounts/doctype/{overdue_payments/overdue_payments.py => overdue_payment/overdue_payment.py} (84%) diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index a0ddf04b6c..b609a5ce14 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -277,7 +277,7 @@ "fieldname": "overdue_payments", "fieldtype": "Table", "label": "Overdue Payments", - "options": "Overdue Payments" + "options": "Overdue Payment" }, { "fieldname": "section_break_28", diff --git a/erpnext/accounts/doctype/overdue_payments/__init__.py b/erpnext/accounts/doctype/overdue_payment/__init__.py similarity index 100% rename from erpnext/accounts/doctype/overdue_payments/__init__.py rename to erpnext/accounts/doctype/overdue_payment/__init__.py diff --git a/erpnext/accounts/doctype/overdue_payments/overdue_payments.json b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json similarity index 99% rename from erpnext/accounts/doctype/overdue_payments/overdue_payments.json rename to erpnext/accounts/doctype/overdue_payment/overdue_payment.json index 57104c186c..e5253bd12f 100644 --- a/erpnext/accounts/doctype/overdue_payments/overdue_payments.json +++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json @@ -161,7 +161,7 @@ "modified": "2021-09-15 19:04:54.082880", "modified_by": "Administrator", "module": "Accounts", - "name": "Overdue Payments", + "name": "Overdue Payment", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/erpnext/accounts/doctype/overdue_payments/overdue_payments.py b/erpnext/accounts/doctype/overdue_payment/overdue_payment.py similarity index 84% rename from erpnext/accounts/doctype/overdue_payments/overdue_payments.py rename to erpnext/accounts/doctype/overdue_payment/overdue_payment.py index 844f8ecdbd..e3820d74e0 100644 --- a/erpnext/accounts/doctype/overdue_payments/overdue_payments.py +++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.py @@ -4,5 +4,5 @@ # import frappe from frappe.model.document import Document -class OverduePayments(Document): +class OverduePayment(Document): pass From 8976e94a1d697a2a9a8930a3fe9274a0443dc176 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 16:38:36 +0200 Subject: [PATCH 005/107] feat: rework doctypes --- erpnext/accounts/doctype/dunning/dunning.json | 80 ++++++++++++++----- .../doctype/dunning_type/dunning_type.json | 33 +++----- 2 files changed, 67 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index b609a5ce14..a0e3c150fd 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -6,7 +6,6 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "title", "naming_series", "customer", "customer_name", @@ -33,18 +32,24 @@ "section_break_12", "overdue_payments", "section_break_28", - "column_break_17", "total_interest", - "total_outstanding", "dunning_fee", + "column_break_17", + "dunning_amount", + "section_break_32", + "spacer", + "column_break_33", + "total_outstanding", "grand_total", - "income_account", - "printing_setting_section", + "printing_settings_section", "language", "body_text", "column_break_22", "letter_head", "closing_text", + "accounting_details_section", + "cost_center", + "income_account", "amended_from" ], "fields": [ @@ -60,7 +65,8 @@ "fieldname": "naming_series", "fieldtype": "Select", "label": "Series", - "options": "DUNN-.MM.-.YY.-" + "options": "DUNN-.MM.-.YY.-", + "print_hide": 1 }, { "fetch_from": "sales_invoice.customer_name", @@ -91,8 +97,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Dunning Type", - "options": "Dunning Type", - "reqd": 1 + "options": "Dunning Type" }, { "fieldname": "column_break_8", @@ -116,11 +121,6 @@ "fieldname": "column_break_17", "fieldtype": "Column Break" }, - { - "fieldname": "printing_setting_section", - "fieldtype": "Section Break", - "label": "Printing Setting" - }, { "fieldname": "language", "fieldtype": "Link", @@ -155,14 +155,6 @@ "print_hide": 1, "read_only": 1 }, - { - "allow_on_submit": 1, - "default": "{customer_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title" - }, { "fieldname": "body_text", "fieldtype": "Text Editor", @@ -260,10 +252,12 @@ "options": "Draft\nResolved\nUnresolved\nCancelled" }, { + "description": "For dunning fee and interest", "fieldname": "income_account", "fieldtype": "Link", "label": "Income Account", - "options": "Account" + "options": "Account", + "print_hide": 1 }, { "fetch_from": "sales_invoice.conversion_rate", @@ -310,6 +304,48 @@ "fieldtype": "Link", "label": "Contact Person", "options": "Contact" + }, + { + "fieldname": "dunning_amount", + "fieldtype": "Currency", + "label": "Dunning Amount", + "options": "currency", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "collapsible": 1, + "fieldname": "printing_settings_section", + "fieldtype": "Section Break", + "label": "Printing Settings" + }, + { + "fieldname": "section_break_32", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_33", + "fieldtype": "Column Break" + }, + { + "fieldname": "spacer", + "fieldtype": "Data", + "hidden": 1, + "label": "Spacer", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "is_submittable": 1, diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json index da43664472..ca33ce58a9 100644 --- a/erpnext/accounts/doctype/dunning_type/dunning_type.json +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json @@ -8,10 +8,7 @@ "engine": "InnoDB", "field_order": [ "dunning_type", - "overdue_interval_section", - "start_day", - "column_break_4", - "end_day", + "is_default", "section_break_6", "dunning_fee", "column_break_8", @@ -45,10 +42,6 @@ "fieldtype": "Table", "options": "Dunning Letter Text" }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "fieldname": "section_break_6", "fieldtype": "Section Break" @@ -57,33 +50,25 @@ "fieldname": "column_break_8", "fieldtype": "Column Break" }, - { - "fieldname": "overdue_interval_section", - "fieldtype": "Section Break", - "label": "Overdue Interval" - }, - { - "fieldname": "start_day", - "fieldtype": "Int", - "label": "Start Day" - }, - { - "fieldname": "end_day", - "fieldtype": "Int", - "label": "End Day" - }, { "fieldname": "rate_of_interest", "fieldtype": "Float", "in_list_view": 1, "label": "Rate of Interest (%) Yearly" + }, + { + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "label": "Is Default" } ], "links": [], - "modified": "2020-07-15 17:14:17.835074", + "modified": "2021-09-16 15:00:02.610605", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning Type", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { From 2ee919220a44dc0390162b46e2b539e3cbc991d2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 16:42:22 +0200 Subject: [PATCH 006/107] feat: rework dunning frontend --- erpnext/accounts/doctype/dunning/dunning.js | 109 +++++++++++--------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 2f997ba02e..5158cc2b7f 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -23,14 +23,12 @@ frappe.ui.form.on("Dunning", { } }; }); + + // cannot add rows manually, only via button "Fetch Overdue Payments" + frm.set_df_property("overdue_payments", "cannot_add_rows", true); }, refresh: function (frm) { frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1); - frm.set_df_property( - "sales_invoice", - "read_only", - frm.doc.__islocal ? 0 : 1 - ); if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") { frm.add_custom_button(__("Resolve"), () => { frm.set_value("status", "Resolved"); @@ -58,25 +56,27 @@ frappe.ui.form.on("Dunning", { frappe.set_route("query-report", "General Ledger"); }, __('View')); } - }, - overdue_days: function (frm) { - frappe.db.get_value( - "Dunning Type", - { - start_day: ["<", frm.doc.overdue_days], - end_day: [">=", frm.doc.overdue_days], - }, - "dunning_type", - (r) => { - if (r) { - frm.set_value("dunning_type", r.dunning_type); - } else { - frm.set_value("dunning_type", ""); - frm.set_value("rate_of_interest", ""); - frm.set_value("dunning_fee", ""); - } - } - ); + + if(frm.doc.docstatus === 0) { + frm.add_custom_button(__("Fetch Overdue Payments"), function() { + erpnext.utils.map_current_doc({ + method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning", + source_doctype: "Sales Invoice", + target: frm, + setters: { + customer: frm.doc.customer || undefined, + }, + get_query_filters: { + docstatus: 1, + status: "Overdue", + company: frm.doc.company + }, + allow_child_item_selection: true, + child_fielname: "payment_schedule", + child_columns: ["due_date", "outstanding"] + }); + }); + } }, dunning_type: function (frm) { frm.trigger("get_dunning_letter_text"); @@ -107,42 +107,43 @@ frappe.ui.form.on("Dunning", { }); } }, - due_date: function (frm) { - frm.trigger("calculate_overdue_days"); - }, posting_date: function (frm) { frm.trigger("calculate_overdue_days"); }, rate_of_interest: function (frm) { - frm.trigger("calculate_interest_and_amount"); - }, - outstanding_amount: function (frm) { - frm.trigger("calculate_interest_and_amount"); - }, - interest_amount: function (frm) { - frm.trigger("calculate_interest_and_amount"); + frm.trigger("calculate_interest_amount"); }, dunning_fee: function (frm) { - frm.trigger("calculate_interest_and_amount"); - }, - sales_invoice: function (frm) { - frm.trigger("calculate_overdue_days"); + frm.trigger("calculate_totals"); }, calculate_overdue_days: function (frm) { - if (frm.doc.posting_date && frm.doc.due_date) { - const overdue_days = moment(frm.doc.posting_date).diff( - frm.doc.due_date, - "days" - ); - frm.set_value("overdue_days", overdue_days); - } + frm.doc.overdue_payments.forEach((row) => { + if (frm.doc.posting_date && row.due_date) { + const overdue_days = moment(frm.doc.posting_date).diff( + row.due_date, + "days" + ); + frappe.model.set_value(row.doctype, row.name, "overdue_days", overdue_days); + } + }); }, - calculate_interest_and_amount: function (frm) { - const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100; - const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount')); - const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount')); - const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total')); - frm.set_value("interest_amount", interest_amount); + calculate_interest_amount: function (frm) { + frm.doc.overdue_payments.forEach((row) => { + const interest_per_year = row.outstanding * frm.doc.rate_of_interest / 100; + const interest_amount = flt((interest_per_year * cint(row.overdue_days)) / 365 || 0, precision("interest_amount")); + frappe.model.set_value(row.doctype, row.name, "interest_amount", interest_amount); + }); + }, + calculate_totals: function (frm) { + const total_interest = frm.doc.overdue_payments + .reduce((prev, cur) => prev + cur.interest_amount, 0); + const total_outstanding = frm.doc.overdue_payments + .reduce((prev, cur) => prev + cur.outstanding, 0); + const dunning_amount = flt(total_interest + frm.doc.dunning_fee, precision('dunning_amount')); + const grand_total = flt(total_outstanding + dunning_amount, precision('grand_total')); + + frm.set_value("total_outstanding", total_outstanding); + frm.set_value("total_interest", total_interest); frm.set_value("dunning_amount", dunning_amount); frm.set_value("grand_total", grand_total); }, @@ -161,3 +162,9 @@ frappe.ui.form.on("Dunning", { }); }, }); + +frappe.ui.form.on("Overdue Payment", { + interest_amount: function(frm, cdt, cdn) { + frm.trigger("calculate_totals"); + } +}); \ No newline at end of file From 2d0dadd9acc370b9559f8d3e70578b6aa29cdf0d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 16:42:51 +0200 Subject: [PATCH 007/107] feat: rework dunning backend --- erpnext/accounts/doctype/dunning/dunning.py | 51 ++++++------- .../doctype/sales_invoice/sales_invoice.py | 75 ++++++++++--------- 2 files changed, 62 insertions(+), 64 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index b4df0a5270..56d49df4be 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -15,25 +15,34 @@ from erpnext.controllers.accounts_controller import AccountsController class Dunning(AccountsController): + def validate(self): - self.validate_overdue_days() - self.validate_amount() + self.validate_overdue_payments() + self.validate_totals() + if not self.income_account: self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account") - def validate_overdue_days(self): - self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0 + def validate_overdue_payments(self): + for row in self.overdue_payments: + row.overdue_days = (getdate(self.posting_date) - getdate(row.due_date)).days or 0 + interest_per_year = flt(row.outstanding) * flt(self.rate_of_interest) / 100 + row.interest_amount = (interest_per_year * cint(row.overdue_days)) / 365 - def validate_amount(self): - amounts = calculate_interest_and_amount( - self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days - ) - if self.interest_amount != amounts.get("interest_amount"): - self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount")) - if self.dunning_amount != amounts.get("dunning_amount"): - self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount")) - if self.grand_total != amounts.get("grand_total"): - self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total")) + def validate_totals(self): + total_outstanding = sum(row.outstanding for row in self.overdue_payments) + total_interest = sum(row.interest_amount for row in self.overdue_payments) + dunning_amount = flt(total_interest) + flt(self.dunning_fee) + grand_total = flt(total_outstanding) + flt(dunning_amount) + + if self.total_outstanding != total_outstanding: + self.total_outstanding = flt(total_outstanding, self.precision('total_outstanding')) + if self.total_interest != total_interest: + self.total_interest = flt(total_interest, self.precision('total_interest')) + if self.dunning_amount != dunning_amount: + self.dunning_amount = flt(dunning_amount, self.precision('dunning_amount')) + if self.grand_total != grand_total: + self.grand_total = flt(grand_total, self.precision('grand_total')) def on_submit(self): self.make_gl_entries() @@ -113,20 +122,6 @@ def resolve_dunning(doc, state): frappe.db.set_value("Dunning", dunning.name, "status", "Resolved") -def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days): - interest_amount = 0 - grand_total = flt(outstanding_amount) + flt(dunning_fee) - if rate_of_interest: - interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100 - interest_amount = (interest_per_year * cint(overdue_days)) / 365 - grand_total += flt(interest_amount) - dunning_amount = flt(interest_amount) + flt(dunning_fee) - return { - "interest_amount": interest_amount, - "grand_total": grand_total, - "dunning_amount": dunning_amount, - } - @frappe.whitelist() def get_dunning_letter_text(dunning_type, doc, language=None): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 2075d57a35..0aa6eab862 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2510,55 +2510,58 @@ def get_mode_of_payment_info(mode_of_payment, company): @frappe.whitelist() -def create_dunning(source_name, target_doc=None): +def create_dunning(source_name, target_doc=None, ignore_permissions=False): from frappe.model.mapper import get_mapped_doc - from erpnext.accounts.doctype.dunning.dunning import ( - calculate_interest_and_amount, - get_dunning_letter_text, - ) + def postprocess_dunning(source, target): + from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text - def set_missing_values(source, target): - target.sales_invoice = source_name - target.outstanding_amount = source.outstanding_amount - overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days - target.overdue_days = overdue_days - if frappe.db.exists( - "Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]} - ): - dunning_type = frappe.get_doc( - "Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]} - ) + dunning_type = frappe.db.exists('Dunning Type', {'is_default': 1}) + if dunning_type: + dunning_type = frappe.get_doc("Dunning Type", dunning_type) target.dunning_type = dunning_type.name target.rate_of_interest = dunning_type.rate_of_interest target.dunning_fee = dunning_type.dunning_fee - letter_text = get_dunning_letter_text(dunning_type=dunning_type.name, doc=target.as_dict()) - if letter_text: - target.body_text = letter_text.get("body_text") - target.closing_text = letter_text.get("closing_text") - target.language = letter_text.get("language") - amounts = calculate_interest_and_amount( - target.outstanding_amount, - target.rate_of_interest, - target.dunning_fee, - target.overdue_days, + letter_text = get_dunning_letter_text( + dunning_type=dunning_type.name, + doc=target.as_dict(), + language=source.language ) - target.interest_amount = amounts.get("interest_amount") - target.dunning_amount = amounts.get("dunning_amount") - target.grand_total = amounts.get("grand_total") - doclist = get_mapped_doc( - "Sales Invoice", - source_name, - { + if letter_text: + target.body_text = letter_text.get('body_text') + target.closing_text = letter_text.get('closing_text') + target.language = letter_text.get('language') + + def postprocess_overdue_payment(source, target, source_parent): + target.overdue_days = (getdate(nowdate()) - getdate(source.due_date)).days + + return get_mapped_doc( + from_doctype="Sales Invoice", + from_docname=source_name, + table_maps={ "Sales Invoice": { "doctype": "Dunning", + "field_map": { + "customer_address": "customer_address", + "parent": "sales_invoice" + }, + }, + "Payment Schedule": { + "doctype": "Overdue Payment", + "field_map": { + "name": "payment_schedule", + "parent": "sales_invoice" + }, + "condition": lambda doc: doc.outstanding > 0, + "postprocess": postprocess_overdue_payment } }, - target_doc, - set_missing_values, + target_doc=target_doc, + postprocess=postprocess_dunning, + ignore_permissions=ignore_permissions ) - return doclist + def check_if_return_invoice_linked_with_payment_entry(self): From 4f51dfe4c53d83d53fc80b8929bf2c35713111df Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 17:21:25 +0200 Subject: [PATCH 008/107] refactor: remove unnecessary code --- erpnext/accounts/doctype/dunning/dunning.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 5158cc2b7f..7bc79e78fb 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -71,9 +71,6 @@ frappe.ui.form.on("Dunning", { status: "Overdue", company: frm.doc.company }, - allow_child_item_selection: true, - child_fielname: "payment_schedule", - child_columns: ["due_date", "outstanding"] }); }); } From db47e1b69c5a2e35633c2017555a92271bd3bf76 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 17:21:52 +0200 Subject: [PATCH 009/107] feat: address and contact queries --- erpnext/accounts/doctype/dunning/dunning.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 7bc79e78fb..73ed9c4261 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -24,6 +24,9 @@ frappe.ui.form.on("Dunning", { }; }); + frm.set_query('contact_person', erpnext.queries.contact_query); + frm.set_query('customer_address', erpnext.queries.address_query); + // cannot add rows manually, only via button "Fetch Overdue Payments" frm.set_df_property("overdue_payments", "cannot_add_rows", true); }, From b186f8e9d7b12ef599ba96db275c7733dcc0f504 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 17:22:25 +0200 Subject: [PATCH 010/107] feat: address display --- erpnext/accounts/doctype/dunning/dunning.js | 6 ++++++ erpnext/accounts/doctype/dunning/dunning.json | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 73ed9c4261..5777583dee 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -78,6 +78,12 @@ frappe.ui.form.on("Dunning", { }); } }, + customer_address: function (frm) { + erpnext.utils.get_address_display(frm, "customer_address"); + }, + company_address: function (frm) { + erpnext.utils.get_address_display(frm, "company_address"); + }, dunning_type: function (frm) { frm.trigger("get_dunning_letter_text"); }, diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index a0e3c150fd..85c73a8a74 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -21,10 +21,11 @@ "address_display", "contact_person", "contact_display", + "column_break_16", + "company_address", + "company_address_display", "contact_mobile", "contact_email", - "column_break_18", - "company_address_display", "section_break_6", "dunning_type", "column_break_8", @@ -206,15 +207,11 @@ "options": "Phone", "read_only": 1 }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, { "fetch_from": "sales_invoice.company_address_display", "fieldname": "company_address_display", "fieldtype": "Small Text", - "label": "Company Address", + "label": "Company Address Display", "read_only": 1 }, { @@ -346,6 +343,16 @@ "print_hide": 1, "read_only": 1, "report_hide": 1 + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_address", + "fieldtype": "Link", + "label": "Company Address", + "options": "Address" } ], "is_submittable": 1, From b07620aacf8fe4e003e8f78bc445b161f27cefc0 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 16 Sep 2021 17:22:46 +0200 Subject: [PATCH 011/107] feat: child table triggers calculation of totals --- erpnext/accounts/doctype/dunning/dunning.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 5777583dee..ede5cf44b1 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -122,6 +122,12 @@ frappe.ui.form.on("Dunning", { dunning_fee: function (frm) { frm.trigger("calculate_totals"); }, + overdue_payments_add: function(frm) { + frm.trigger("calculate_totals"); + }, + overdue_payments_remove: function (frm) { + frm.trigger("calculate_totals"); + }, calculate_overdue_days: function (frm) { frm.doc.overdue_payments.forEach((row) => { if (frm.doc.posting_date && row.due_date) { From 9016baddcaaeb89c7bc4246bd28aef2a40a5b819 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:43:09 +0200 Subject: [PATCH 012/107] feat: company address query + style --- erpnext/accounts/doctype/dunning/dunning.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index ede5cf44b1..2ddfe80e61 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -24,8 +24,9 @@ frappe.ui.form.on("Dunning", { }; }); - frm.set_query('contact_person', erpnext.queries.contact_query); - frm.set_query('customer_address', erpnext.queries.address_query); + frm.set_query("contact_person", erpnext.queries.contact_query); + frm.set_query("customer_address", erpnext.queries.address_query); + frm.set_query("company_address", erpnext.queries.company_address_query); // cannot add rows manually, only via button "Fetch Overdue Payments" frm.set_df_property("overdue_payments", "cannot_add_rows", true); @@ -48,7 +49,7 @@ frappe.ui.form.on("Dunning", { } if(frm.doc.docstatus > 0) { - frm.add_custom_button(__('Ledger'), function() { + frm.add_custom_button(__("Ledger"), function() { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, @@ -57,7 +58,7 @@ frappe.ui.form.on("Dunning", { "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); - }, __('View')); + }, __("View")); } if(frm.doc.docstatus === 0) { @@ -151,8 +152,8 @@ frappe.ui.form.on("Dunning", { .reduce((prev, cur) => prev + cur.interest_amount, 0); const total_outstanding = frm.doc.overdue_payments .reduce((prev, cur) => prev + cur.outstanding, 0); - const dunning_amount = flt(total_interest + frm.doc.dunning_fee, precision('dunning_amount')); - const grand_total = flt(total_outstanding + dunning_amount, precision('grand_total')); + const dunning_amount = flt(total_interest + frm.doc.dunning_fee, precision("dunning_amount")); + const grand_total = flt(total_outstanding + dunning_amount, precision("grand_total")); frm.set_value("total_outstanding", total_outstanding); frm.set_value("total_interest", total_interest); From 938f7d2266bde259c40c889014a0139c31e3138f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:58:24 +0200 Subject: [PATCH 013/107] reafctor: validate instead of postprocess --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0aa6eab862..f240fe9b19 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2533,8 +2533,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.closing_text = letter_text.get('closing_text') target.language = letter_text.get('language') - def postprocess_overdue_payment(source, target, source_parent): - target.overdue_days = (getdate(nowdate()) - getdate(source.due_date)).days + target.validate() return get_mapped_doc( from_doctype="Sales Invoice", @@ -2553,8 +2552,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): "name": "payment_schedule", "parent": "sales_invoice" }, - "condition": lambda doc: doc.outstanding > 0, - "postprocess": postprocess_overdue_payment + "condition": lambda doc: doc.outstanding > 0 } }, target_doc=target_doc, From 043066a2c8deabd1df2525e86f6558b6566528dd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 17 Sep 2021 12:03:02 +0200 Subject: [PATCH 014/107] style: use double quotes --- erpnext/accounts/doctype/dunning/dunning.py | 10 +++++----- .../accounts/doctype/sales_invoice/sales_invoice.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 56d49df4be..79de03cc71 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -21,7 +21,7 @@ class Dunning(AccountsController): self.validate_totals() if not self.income_account: - self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account") + self.income_account = frappe.db.get_value("Company", self.company, "default_income_account") def validate_overdue_payments(self): for row in self.overdue_payments: @@ -36,13 +36,13 @@ class Dunning(AccountsController): grand_total = flt(total_outstanding) + flt(dunning_amount) if self.total_outstanding != total_outstanding: - self.total_outstanding = flt(total_outstanding, self.precision('total_outstanding')) + self.total_outstanding = flt(total_outstanding, self.precision("total_outstanding")) if self.total_interest != total_interest: - self.total_interest = flt(total_interest, self.precision('total_interest')) + self.total_interest = flt(total_interest, self.precision("total_interest")) if self.dunning_amount != dunning_amount: - self.dunning_amount = flt(dunning_amount, self.precision('dunning_amount')) + self.dunning_amount = flt(dunning_amount, self.precision("dunning_amount")) if self.grand_total != grand_total: - self.grand_total = flt(grand_total, self.precision('grand_total')) + self.grand_total = flt(grand_total, self.precision("grand_total")) def on_submit(self): self.make_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f240fe9b19..05f8638794 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2516,7 +2516,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): def postprocess_dunning(source, target): from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text - dunning_type = frappe.db.exists('Dunning Type', {'is_default': 1}) + dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1}) if dunning_type: dunning_type = frappe.get_doc("Dunning Type", dunning_type) target.dunning_type = dunning_type.name @@ -2529,9 +2529,9 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): ) if letter_text: - target.body_text = letter_text.get('body_text') - target.closing_text = letter_text.get('closing_text') - target.language = letter_text.get('language') + target.body_text = letter_text.get("body_text") + target.closing_text = letter_text.get("closing_text") + target.language = letter_text.get("language") target.validate() From 676ed6b881fcd02f10cb7e207e46119189ed55ca Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 17 Sep 2021 12:16:23 +0200 Subject: [PATCH 015/107] feat: hide fields in print --- erpnext/accounts/doctype/dunning/dunning.json | 18 ++++++++++++------ .../overdue_payment/overdue_payment.json | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index 85c73a8a74..2f880d115d 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -126,13 +126,15 @@ "fieldname": "language", "fieldtype": "Link", "label": "Print Language", - "options": "Language" + "options": "Language", + "print_hide": 1 }, { "fieldname": "letter_head", "fieldtype": "Link", "label": "Letter Head", - "options": "Letter Head" + "options": "Letter Head", + "print_hide": 1 }, { "fieldname": "column_break_22", @@ -294,13 +296,15 @@ "fieldname": "customer_address", "fieldtype": "Link", "label": "Customer Address", - "options": "Address" + "options": "Address", + "print_hide": 1 }, { "fieldname": "contact_person", "fieldtype": "Link", "label": "Contact Person", - "options": "Contact" + "options": "Contact", + "print_hide": 1 }, { "fieldname": "dunning_amount", @@ -319,7 +323,8 @@ "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "print_hide": 1 }, { "collapsible": 1, @@ -352,7 +357,8 @@ "fieldname": "company_address", "fieldtype": "Link", "label": "Company Address", - "options": "Address" + "options": "Address", + "print_hide": 1 } ], "is_submittable": 1, diff --git a/erpnext/accounts/doctype/overdue_payment/overdue_payment.json b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json index e5253bd12f..bc351d835a 100644 --- a/erpnext/accounts/doctype/overdue_payment/overdue_payment.json +++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json @@ -113,6 +113,7 @@ "fieldname": "discounted_amount", "fieldtype": "Currency", "label": "Discounted Amount", + "print_hide": 1, "read_only": 1 }, { @@ -124,6 +125,7 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency", + "print_hide": 1, "read_only": 1 }, { @@ -138,6 +140,7 @@ "fieldname": "payment_schedule", "fieldtype": "Data", "label": "Payment Schedule", + "print_hide": 1, "read_only": 1 }, { @@ -158,7 +161,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-15 19:04:54.082880", + "modified": "2021-09-17 12:10:42.278923", "modified_by": "Administrator", "module": "Accounts", "name": "Overdue Payment", From f143fe7dccc940c6d5b73d4eb3369089d4bfc1dc Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:34:09 +0200 Subject: [PATCH 016/107] refactor: tests --- .../accounts/doctype/dunning/test_dunning.py | 133 +++++++----------- 1 file changed, 50 insertions(+), 83 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index e1fd1e984f..956b1cfdbe 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -6,7 +6,7 @@ import unittest import frappe from frappe.utils import add_days, nowdate, today -from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount +from erpnext.accounts.doctype.sales_invoice.sales_invoice import create_dunning as create_dunning_from_sales_invoice from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( unlink_payment_on_cancel_of_invoice, @@ -19,34 +19,34 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( class TestDunning(unittest.TestCase): @classmethod def setUpClass(self): - create_dunning_type() - create_dunning_type_with_zero_interest_rate() + create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1) + create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0) unlink_payment_on_cancel_of_invoice() @classmethod def tearDownClass(self): unlink_payment_on_cancel_of_invoice(0) - def test_dunning(self): - dunning = create_dunning() - amounts = calculate_interest_and_amount( - dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days - ) - self.assertEqual(round(amounts.get("interest_amount"), 2), 0.44) - self.assertEqual(round(amounts.get("dunning_amount"), 2), 20.44) - self.assertEqual(round(amounts.get("grand_total"), 2), 120.44) + def test_first_dunning(self): + dunning = create_first_dunning() - def test_dunning_with_zero_interest_rate(self): - dunning = create_dunning_with_zero_interest_rate() - amounts = calculate_interest_and_amount( - dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days - ) - self.assertEqual(round(amounts.get("interest_amount"), 2), 0) - self.assertEqual(round(amounts.get("dunning_amount"), 2), 20) - self.assertEqual(round(amounts.get("grand_total"), 2), 120) + self.assertEqual(round(dunning.total_outstanding, 2), 100.00) + self.assertEqual(round(dunning.total_interest, 2), 0.00) + self.assertEqual(round(dunning.dunning_fee, 2), 0.00) + self.assertEqual(round(dunning.dunning_amount, 2), 0.00) + self.assertEqual(round(dunning.grand_total, 2), 100.00) + + def test_second_dunning(self): + dunning = create_second_dunning() + + self.assertEqual(round(dunning.total_outstanding, 2), 100.00) + self.assertEqual(round(dunning.total_interest, 2), 0.41) + self.assertEqual(round(dunning.dunning_fee, 2), 10.00) + self.assertEqual(round(dunning.dunning_amount, 2), 10.41) + self.assertEqual(round(dunning.grand_total, 2), 110.41) def test_gl_entries(self): - dunning = create_dunning() + dunning = create_second_dunning() dunning.submit() gl_entries = frappe.db.sql( """select account, debit, credit @@ -56,16 +56,17 @@ class TestDunning(unittest.TestCase): as_dict=1, ) self.assertTrue(gl_entries) - expected_values = dict( - (d[0], d) for d in [["Debtors - _TC", 20.44, 0.0], ["Sales - _TC", 0.0, 20.44]] - ) + expected_values = dict((d[0], d) for d in [ + ['Debtors - _TC', 10.41, 0.0], + ['Sales - _TC', 0.0, 10.41] + ]) for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.account) self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) def test_payment_entry(self): - dunning = create_dunning() + dunning = create_second_dunning() dunning.submit() pe = get_payment_entry("Dunning", dunning.name) pe.reference_no = "1" @@ -80,83 +81,49 @@ class TestDunning(unittest.TestCase): self.assertEqual(si_doc.outstanding_amount, 0) -def create_dunning(): +def create_first_dunning(): posting_date = add_days(today(), -20) due_date = add_days(today(), -15) sales_invoice = create_sales_invoice_against_cost_center( - posting_date=posting_date, due_date=due_date, status="Overdue" - ) - dunning_type = frappe.get_doc("Dunning Type", "First Notice") - dunning = frappe.new_doc("Dunning") - dunning.sales_invoice = sales_invoice.name - dunning.customer_name = sales_invoice.customer_name - dunning.outstanding_amount = sales_invoice.outstanding_amount - dunning.debit_to = sales_invoice.debit_to - dunning.currency = sales_invoice.currency - dunning.company = sales_invoice.company - dunning.posting_date = nowdate() - dunning.due_date = sales_invoice.due_date - dunning.dunning_type = "First Notice" - dunning.rate_of_interest = dunning_type.rate_of_interest - dunning.dunning_fee = dunning_type.dunning_fee + posting_date=posting_date, due_date=due_date, qty=1, rate=100) + dunning = create_dunning_from_sales_invoice(sales_invoice.name) dunning.save() + return dunning -def create_dunning_with_zero_interest_rate(): +def create_second_dunning(): posting_date = add_days(today(), -20) due_date = add_days(today(), -15) sales_invoice = create_sales_invoice_against_cost_center( - posting_date=posting_date, due_date=due_date, status="Overdue" - ) - dunning_type = frappe.get_doc("Dunning Type", "First Notice with 0% Rate of Interest") - dunning = frappe.new_doc("Dunning") - dunning.sales_invoice = sales_invoice.name - dunning.customer_name = sales_invoice.customer_name - dunning.outstanding_amount = sales_invoice.outstanding_amount - dunning.debit_to = sales_invoice.debit_to - dunning.currency = sales_invoice.currency - dunning.company = sales_invoice.company - dunning.posting_date = nowdate() - dunning.due_date = sales_invoice.due_date - dunning.dunning_type = "First Notice with 0% Rate of Interest" + posting_date=posting_date, due_date=due_date, qty=1, rate=100) + dunning = create_dunning_from_sales_invoice(sales_invoice.name) + dunning_type = frappe.get_doc("Dunning Type", "Second Notice") + + dunning.dunning_type = dunning_type.name dunning.rate_of_interest = dunning_type.rate_of_interest dunning.dunning_fee = dunning_type.dunning_fee dunning.save() + return dunning -def create_dunning_type(): +def create_dunning_type(title, fee, interest, is_default): + existing = frappe.db.exists("Dunning Type", title) + if existing: + return frappe.get_doc("Dunning Type", existing) + dunning_type = frappe.new_doc("Dunning Type") - dunning_type.dunning_type = "First Notice" - dunning_type.start_day = 10 - dunning_type.end_day = 20 - dunning_type.dunning_fee = 20 - dunning_type.rate_of_interest = 8 + dunning_type.dunning_type = title + dunning_type.is_default = is_default + dunning_type.dunning_fee = fee + dunning_type.rate_of_interest = interest dunning_type.append( - "dunning_letter_text", - { + "dunning_letter_text", { "language": "en", - "body_text": "We have still not received payment for our invoice ", - "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.", - }, - ) - dunning_type.save() - - -def create_dunning_type_with_zero_interest_rate(): - dunning_type = frappe.new_doc("Dunning Type") - dunning_type.dunning_type = "First Notice with 0% Rate of Interest" - dunning_type.start_day = 10 - dunning_type.end_day = 20 - dunning_type.dunning_fee = 20 - dunning_type.rate_of_interest = 0 - dunning_type.append( - "dunning_letter_text", - { - "language": "en", - "body_text": "We have still not received payment for our invoice ", - "closing_text": "We kindly request that you pay the outstanding amount immediately, and late fees.", - }, + "body_text": "We have still not received payment for our invoice", + "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees." + } ) dunning_type.save() + return dunning_type From 24e7a218392111ca3bbed85412adf935f9cd4496 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:34:28 +0200 Subject: [PATCH 017/107] refactor: remove redndant argument --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 05f8638794..f8f7c3666a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2555,7 +2555,6 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): "condition": lambda doc: doc.outstanding > 0 } }, - target_doc=target_doc, postprocess=postprocess_dunning, ignore_permissions=ignore_permissions ) From df840cca75d3350c54230c6d67734f9b9e707073 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 23 Sep 2021 20:04:34 +0200 Subject: [PATCH 018/107] refactor: validate totals --- erpnext/accounts/doctype/dunning/dunning.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 79de03cc71..e4b502a166 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -30,19 +30,10 @@ class Dunning(AccountsController): row.interest_amount = (interest_per_year * cint(row.overdue_days)) / 365 def validate_totals(self): - total_outstanding = sum(row.outstanding for row in self.overdue_payments) - total_interest = sum(row.interest_amount for row in self.overdue_payments) - dunning_amount = flt(total_interest) + flt(self.dunning_fee) - grand_total = flt(total_outstanding) + flt(dunning_amount) - - if self.total_outstanding != total_outstanding: - self.total_outstanding = flt(total_outstanding, self.precision("total_outstanding")) - if self.total_interest != total_interest: - self.total_interest = flt(total_interest, self.precision("total_interest")) - if self.dunning_amount != dunning_amount: - self.dunning_amount = flt(dunning_amount, self.precision("dunning_amount")) - if self.grand_total != grand_total: - self.grand_total = flt(grand_total, self.precision("grand_total")) + self.total_outstanding = sum(row.outstanding for row in self.overdue_payments) + self.total_interest = sum(row.interest for row in self.overdue_payments) + self.dunning_amount = self.total_interest + self.dunning_fee + self.grand_total = self.total_outstanding + self.dunning_amount def on_submit(self): self.make_gl_entries() From be5fb94837a8fb894c2e649d18d33c4674545474 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 23 Sep 2021 20:09:53 +0200 Subject: [PATCH 019/107] feat: currency section , debit_to, base_dunning_amount --- erpnext/accounts/doctype/dunning/dunning.js | 117 +++++++++++++++++- erpnext/accounts/doctype/dunning/dunning.json | 65 +++++++--- 2 files changed, 156 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 2ddfe80e61..45fcc4356d 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -23,7 +23,15 @@ frappe.ui.form.on("Dunning", { } }; }); - + frm.set_query("debit_to", () => { + return { + filters: { + "account_type": "Receivable", + "is_group": 0, + "company": frm.doc.company + } + } + }); frm.set_query("contact_person", erpnext.queries.contact_query); frm.set_query("customer_address", erpnext.queries.address_query); frm.set_query("company_address", erpnext.queries.company_address_query); @@ -43,13 +51,13 @@ frappe.ui.form.on("Dunning", { __("Payment"), function () { frm.events.make_payment_entry(frm); - },__("Create") + }, __("Create") ); frm.page.set_inner_btn_group_as_primary(__("Create")); } - if(frm.doc.docstatus > 0) { - frm.add_custom_button(__("Ledger"), function() { + if (frm.doc.docstatus > 0) { + frm.add_custom_button(__("Ledger"), function () { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, @@ -61,8 +69,8 @@ frappe.ui.form.on("Dunning", { }, __("View")); } - if(frm.doc.docstatus === 0) { - frm.add_custom_button(__("Fetch Overdue Payments"), function() { + if (frm.doc.docstatus === 0) { + frm.add_custom_button(__("Fetch Overdue Payments"), function () { erpnext.utils.map_current_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning", source_doctype: "Sales Invoice", @@ -78,6 +86,103 @@ frappe.ui.form.on("Dunning", { }); }); } + + frappe.dynamic_link = { doc: frm.doc, fieldname: 'customer', doctype: 'Customer' } + + frm.toggle_display("customer_name", (frm.doc.customer_name && frm.doc.customer_name !== frm.doc.customer)); + }, + // When multiple companies are set up. in case company name is changed set default company address + company: function (frm) { + if (frm.doc.company) { + frappe.call({ + method: "erpnext.setup.doctype.company.company.get_default_company_address", + args: { name: frm.doc.company, existing_address: frm.doc.company_address || "" }, + debounce: 2000, + callback: function (r) { + if (r.message) { + frm.set_value("company_address", r.message) + } + else { + frm.set_value("company_address", "") + } + } + }); + + if (frm.fields_dict.currency) { + var company_currency = erpnext.get_currency(frm.doc.company); + + if (!frm.doc.currency) { + frm.set_value("currency", company_currency); + } + + if (frm.doc.currency == company_currency) { + frm.set_value("conversion_rate", 1.0); + } + } + + var company_doc = frappe.get_doc(":Company", frm.doc.company); + if (company_doc.default_letter_head) { + if (frm.fields_dict.letter_head) { + frm.set_value("letter_head", company_doc.default_letter_head); + } + } + } + frm.trigger("set_debit_to"); + }, + set_debit_to: function(frm) { + if (frm.doc.customer && frm.doc.company) { + return frappe.call({ + method: "erpnext.accounts.party.get_party_account", + args: { + company: frm.doc.company, + party_type: "Customer", + party: frm.doc.customer, + currency: erpnext.get_currency(frm.doc.company) + }, + callback: function (r) { + if (!r.exc && r.message) { + frm.set_value("debit_to", r.message); + } + } + }); + } + }, + customer: function (frm) { + frm.trigger("set_debit_to"); + }, + currency: function (frm) { + // this.set_dynamic_labels(); + var company_currency = erpnext.get_currency(frm.doc.company); + // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc + if(frm.doc.currency && frm.doc.currency !== company_currency) { + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + transaction_date: transaction_date, + from_currency: frm.doc.currency, + to_currency: company_currency, + args: "for_selling" + }, + freeze: true, + freeze_message: __("Fetching exchange rates ..."), + callback: function(r) { + const exchange_rate = flt(r.message); + if(exchange_rate != frm.doc.conversion_rate) { + frm.set_value("conversion_rate", exchange_rate); + } + } + }); + } else { + frm.trigger("conversion_rate"); + } + }, + conversion_rate: function (frm) { + if(frm.doc.currency === erpnext.get_currency(frm.doc.company)) { + frm.set_value("conversion_rate", 1.0); + } + + // Make read only if Accounts Settings doesn't allow stale rates + frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1); }, customer_address: function (frm) { erpnext.utils.get_address_display(frm, "customer_address"); diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index 2f880d115d..1dd05b77fa 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -9,13 +9,15 @@ "naming_series", "customer", "customer_name", - "currency", - "conversion_rate", "column_break_3", "company", "posting_date", "posting_time", "status", + "section_break_9", + "currency", + "column_break_11", + "conversion_rate", "address_and_contact_section", "customer_address", "address_display", @@ -37,6 +39,7 @@ "dunning_fee", "column_break_17", "dunning_amount", + "base_dunning_amount", "section_break_32", "spacer", "column_break_33", @@ -51,6 +54,7 @@ "accounting_details_section", "cost_center", "income_account", + "debit_to", "amended_from" ], "fields": [ @@ -140,15 +144,6 @@ "fieldname": "column_break_22", "fieldtype": "Column Break" }, - { - "fetch_from": "sales_invoice.currency", - "fieldname": "currency", - "fieldtype": "Link", - "hidden": 1, - "label": "Currency", - "options": "Currency", - "read_only": 1 - }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -248,7 +243,8 @@ "fieldtype": "Select", "in_standard_filter": 1, "label": "Status", - "options": "Draft\nResolved\nUnresolved\nCancelled" + "options": "Draft\nResolved\nUnresolved\nCancelled", + "read_only": 1 }, { "description": "For dunning fee and interest", @@ -258,14 +254,6 @@ "options": "Account", "print_hide": 1 }, - { - "fetch_from": "sales_invoice.conversion_rate", - "fieldname": "conversion_rate", - "fieldtype": "Float", - "hidden": 1, - "label": "Conversion Rate", - "read_only": 1 - }, { "fieldname": "overdue_payments", "fieldtype": "Table", @@ -307,6 +295,7 @@ "print_hide": 1 }, { + "default": "0", "fieldname": "dunning_amount", "fieldtype": "Currency", "label": "Dunning Amount", @@ -359,6 +348,42 @@ "label": "Company Address", "options": "Address", "print_hide": 1 + }, + { + "fieldname": "debit_to", + "fieldtype": "Link", + "label": "Debit To", + "options": "Account", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Currency" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Conversion Rate" + }, + { + "default": "0", + "fieldname": "base_dunning_amount", + "fieldtype": "Currency", + "label": "Dunning Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "is_submittable": 1, From bc40f3f425804595c50121aac2f9422dd014d0c1 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 23 Sep 2021 20:13:52 +0200 Subject: [PATCH 020/107] refactor: rename interest_amount to interest, dunning_level --- erpnext/accounts/doctype/dunning/dunning.js | 20 ++++----- erpnext/accounts/doctype/dunning/dunning.py | 5 ++- .../overdue_payment/overdue_payment.json | 44 +++++++++---------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 45fcc4356d..98462b89db 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -200,7 +200,7 @@ frappe.ui.form.on("Dunning", { if (frm.doc.dunning_type) { frappe.call({ method: - "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text", + "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text", args: { dunning_type: frm.doc.dunning_type, language: frm.doc.language, @@ -223,12 +223,12 @@ frappe.ui.form.on("Dunning", { frm.trigger("calculate_overdue_days"); }, rate_of_interest: function (frm) { - frm.trigger("calculate_interest_amount"); + frm.trigger("calculate_interest"); }, dunning_fee: function (frm) { frm.trigger("calculate_totals"); }, - overdue_payments_add: function(frm) { + overdue_payments_add: function (frm) { frm.trigger("calculate_totals"); }, overdue_payments_remove: function (frm) { @@ -245,16 +245,16 @@ frappe.ui.form.on("Dunning", { } }); }, - calculate_interest_amount: function (frm) { + calculate_interest: function (frm) { frm.doc.overdue_payments.forEach((row) => { - const interest_per_year = row.outstanding * frm.doc.rate_of_interest / 100; - const interest_amount = flt((interest_per_year * cint(row.overdue_days)) / 365 || 0, precision("interest_amount")); - frappe.model.set_value(row.doctype, row.name, "interest_amount", interest_amount); + const interest_per_day = frm.doc.rate_of_interest / 100 / 365; + const interest = flt((interest_per_day * row.outstanding * cint(row.overdue_days)) / 365 || 0, precision("interest")); + frappe.model.set_value(row.doctype, row.name, "interest", interest); }); }, calculate_totals: function (frm) { const total_interest = frm.doc.overdue_payments - .reduce((prev, cur) => prev + cur.interest_amount, 0); + .reduce((prev, cur) => prev + cur.interest, 0); const total_outstanding = frm.doc.overdue_payments .reduce((prev, cur) => prev + cur.outstanding, 0); const dunning_amount = flt(total_interest + frm.doc.dunning_fee, precision("dunning_amount")); @@ -268,7 +268,7 @@ frappe.ui.form.on("Dunning", { make_payment_entry: function (frm) { return frappe.call({ method: - "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry", + "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry", args: { dt: frm.doc.doctype, dn: frm.doc.name, @@ -282,7 +282,7 @@ frappe.ui.form.on("Dunning", { }); frappe.ui.form.on("Overdue Payment", { - interest_amount: function(frm, cdt, cdn) { + interest: function (frm, cdt, cdn) { frm.trigger("calculate_totals"); } }); \ No newline at end of file diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index e4b502a166..f1283ae06f 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -24,10 +24,11 @@ class Dunning(AccountsController): self.income_account = frappe.db.get_value("Company", self.company, "default_income_account") def validate_overdue_payments(self): + daily_interest = self.rate_of_interest / 100 / 365 + for row in self.overdue_payments: row.overdue_days = (getdate(self.posting_date) - getdate(row.due_date)).days or 0 - interest_per_year = flt(row.outstanding) * flt(self.rate_of_interest) / 100 - row.interest_amount = (interest_per_year * cint(row.overdue_days)) / 365 + row.interest = row.outstanding * daily_interest * row.overdue_days def validate_totals(self): self.total_outstanding = sum(row.outstanding for row in self.overdue_payments) diff --git a/erpnext/accounts/doctype/overdue_payment/overdue_payment.json b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json index bc351d835a..99e16469d0 100644 --- a/erpnext/accounts/doctype/overdue_payment/overdue_payment.json +++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json @@ -7,6 +7,7 @@ "field_order": [ "sales_invoice", "payment_schedule", + "dunning_level", "payment_term", "section_break_15", "description", @@ -16,21 +17,18 @@ "mode_of_payment", "column_break_5", "invoice_portion", - "section_break_9", + "section_break_16", "payment_amount", "outstanding", "paid_amount", "discounted_amount", - "column_break_3", - "base_payment_amount", - "interest_amount" + "interest" ], "fields": [ { "columns": 2, "fieldname": "payment_term", "fieldtype": "Link", - "in_list_view": 1, "label": "Payment Term", "options": "Payment Term", "print_hide": 1, @@ -79,10 +77,6 @@ "label": "Invoice Portion", "read_only": 1 }, - { - "fieldname": "section_break_9", - "fieldtype": "Section Break" - }, { "columns": 2, "fieldname": "payment_amount", @@ -116,24 +110,13 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "base_payment_amount", - "fieldtype": "Currency", - "label": "Payment Amount (Company Currency)", - "options": "Company:company:default_currency", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "sales_invoice", "fieldtype": "Link", "in_list_view": 1, "label": "Sales Invoice", "options": "Sales Invoice", + "read_only": 1, "reqd": 1 }, { @@ -151,17 +134,30 @@ "read_only": 1 }, { - "fieldname": "interest_amount", + "default": "1", + "fieldname": "dunning_level", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Dunning Level", + "read_only": 1 + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break" + }, + { + "fieldname": "interest", "fieldtype": "Currency", "in_list_view": 1, - "label": "Interest Amount", + "label": "Interest", + "options": "currency", "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-17 12:10:42.278923", + "modified": "2021-09-23 13:48:27.898830", "modified_by": "Administrator", "module": "Accounts", "name": "Overdue Payment", From 3895c03ba9305e02806272c7793430559d1d699f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 23 Sep 2021 20:14:45 +0200 Subject: [PATCH 021/107] feat: change make_gl_entries to work with new data structure --- erpnext/accounts/doctype/dunning/dunning.py | 62 ++++++--------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index f1283ae06f..5194090743 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -5,11 +5,8 @@ import json import frappe -from frappe.utils import cint, flt, getdate +from frappe.utils import getdate -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( - get_accounting_dimensions, -) from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries from erpnext.controllers.accounts_controller import AccountsController @@ -47,57 +44,34 @@ class Dunning(AccountsController): def make_gl_entries(self): if not self.dunning_amount: return - gl_entries = [] - invoice_fields = [ - "project", - "cost_center", - "debit_to", - "party_account_currency", - "conversion_rate", - "cost_center", - ] - inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1) - accounting_dimensions = get_accounting_dimensions() - invoice_fields.extend(accounting_dimensions) + cost_center = self.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate) - default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center") - - gl_entries.append( - self.get_gl_dict( - { - "account": inv.debit_to, + make_gl_entries( + [ + self.get_gl_dict({ + "account": self.debit_to, "party_type": "Customer", "party": self.customer, "due_date": self.due_date, "against": self.income_account, - "debit": dunning_in_company_currency, + "debit": self.dunning_amount, "debit_in_account_currency": self.dunning_amount, "against_voucher": self.name, "against_voucher_type": "Dunning", - "cost_center": inv.cost_center or default_cost_center, - "project": inv.project, - }, - inv.party_account_currency, - item=inv, - ) - ) - gl_entries.append( - self.get_gl_dict( - { + "cost_center": cost_center + }), + self.get_gl_dict({ "account": self.income_account, "against": self.customer, - "credit": dunning_in_company_currency, - "cost_center": inv.cost_center or default_cost_center, - "credit_in_account_currency": self.dunning_amount, - "project": inv.project, - }, - item=inv, - ) - ) - make_gl_entries( - gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False + "credit": self.dunning_amount, + "cost_center": cost_center, + "credit_in_account_currency": self.dunning_amount + }) + ], + cancel=(self.docstatus == 2), + update_outstanding="No", + merge_entries=False ) From 603117eb6bcd4319fc371f562bec0e96f2fbddbb Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Sep 2021 16:23:18 +0200 Subject: [PATCH 022/107] feat: change print format to reflect doctype changes --- .../accounts/print_format/dunning_letter/dunning_letter.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json index a7eac70b65..c48e1cf35b 100644 --- a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json +++ b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json @@ -1,4 +1,5 @@ { + "absolute_value": 0, "align_labels_right": 0, "creation": "2019-12-11 04:37:14.012805", "css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n", @@ -9,10 +10,10 @@ "docstatus": 0, "doctype": "Print Format", "font": "Arial", - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{{doc.customer_name}}
\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n
{{_(doc.dunning_type)}}
\\n
{{ doc.name }}
\\n
\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n \\n \\n \\n\\t \\n \\n \\n \\n \\n \\n {%if doc.rate_of_interest > 0%}\\n \\n \\n \\n \\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n \\n \\n \\n \\n {% endif %}\\n \\n
{{_(\\\"Description\\\")}}{{_(\\\"Amount\\\")}}
\\n {{_(\\\"Outstanding Amount\\\")}}\\n \\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n
\\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n \\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n
\\n {{_(\\\"Dunning Fee\\\")}}\\n \\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n
\\n\\t\\t
\\n\\t\\t\\t{{_(\\\"Grand Total\\\")}}
\\n\\t\\t
\\n\\t\\t\\t{{doc.get_formatted(\\\"grand_total\\\")}}\\n\\t\\t
\\n
\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{{doc.customer_name}}
\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n
{{_(doc.dunning_type)}}
\\n
{{ doc.name }}
\\n
\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"overdue_payments\", \"print_hide\": 0, \"label\": \"Overdue Payments\", \"visible_columns\": [{\"fieldname\": \"sales_invoice\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"dunning_level\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"due_date\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"overdue_days\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"invoice_portion\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"outstanding\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"interest\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_outstanding\", \"print_hide\": 0, \"label\": \"Total Outstanding\"}, {\"fieldname\": \"dunning_fee\", \"print_hide\": 0, \"label\": \"Dunning Fee\"}, {\"fieldname\": \"total_interest\", \"print_hide\": 0, \"label\": \"Total Interest\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]", "idx": 0, "line_breaks": 0, - "modified": "2020-07-14 18:25:44.348207", + "modified": "2021-09-30 10:22:02.603871", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning Letter", From 16a23d9f0f69ce532ea406dee2b8421b9803c456 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Sep 2021 17:37:35 +0200 Subject: [PATCH 023/107] refactor: dunning --- erpnext/accounts/doctype/dunning/dunning.js | 35 ++++---- erpnext/accounts/doctype/dunning/dunning.py | 80 +++++++++---------- .../doctype/payment_entry/payment_entry.py | 36 +++------ 3 files changed, 65 insertions(+), 86 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 98462b89db..5cee711950 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -56,19 +56,6 @@ frappe.ui.form.on("Dunning", { frm.page.set_inner_btn_group_as_primary(__("Create")); } - if (frm.doc.docstatus > 0) { - frm.add_custom_button(__("Ledger"), function () { - frappe.route_options = { - "voucher_no": frm.doc.name, - "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, - "company": frm.doc.company, - "show_cancelled_entries": frm.doc.docstatus === 2 - }; - frappe.set_route("query-report", "General Ledger"); - }, __("View")); - } - if (frm.doc.docstatus === 0) { frm.add_custom_button(__("Fetch Overdue Payments"), function () { erpnext.utils.map_current_doc({ @@ -248,22 +235,29 @@ frappe.ui.form.on("Dunning", { calculate_interest: function (frm) { frm.doc.overdue_payments.forEach((row) => { const interest_per_day = frm.doc.rate_of_interest / 100 / 365; - const interest = flt((interest_per_day * row.outstanding * cint(row.overdue_days)) / 365 || 0, precision("interest")); + const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest")); frappe.model.set_value(row.doctype, row.name, "interest", interest); }); }, calculate_totals: function (frm) { + debugger; const total_interest = frm.doc.overdue_payments .reduce((prev, cur) => prev + cur.interest, 0); const total_outstanding = frm.doc.overdue_payments .reduce((prev, cur) => prev + cur.outstanding, 0); - const dunning_amount = flt(total_interest + frm.doc.dunning_fee, precision("dunning_amount")); - const grand_total = flt(total_outstanding + dunning_amount, precision("grand_total")); + const dunning_amount = total_interest + frm.doc.dunning_fee; + const base_dunning_amount = dunning_amount * frm.doc.conversion_rate; + const grand_total = total_outstanding + dunning_amount; - frm.set_value("total_outstanding", total_outstanding); - frm.set_value("total_interest", total_interest); - frm.set_value("dunning_amount", dunning_amount); - frm.set_value("grand_total", grand_total); + function setWithPrecison(field, value) { + frm.set_value(field, flt(value, precision(field))); + } + + setWithPrecison("total_outstanding", total_outstanding); + setWithPrecison("total_interest", total_interest); + setWithPrecison("dunning_amount", dunning_amount); + setWithPrecison("base_dunning_amount", base_dunning_amount); + setWithPrecison("grand_total", grand_total); }, make_payment_entry: function (frm) { return frappe.call({ @@ -283,6 +277,7 @@ frappe.ui.form.on("Dunning", { frappe.ui.form.on("Overdue Payment", { interest: function (frm, cdt, cdn) { + debugger; frm.trigger("calculate_totals"); } }); \ No newline at end of file diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 5194090743..ec116f3061 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -1,24 +1,44 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +""" +# Accounting +1. Payment of outstanding invoices with dunning amount + + - Debit full amount to bank + - Credit invoiced amount to receivables + - Credit dunning amount to interest and similar revenue + + -> Resolves dunning automatically +""" +from __future__ import unicode_literals import json import frappe + +from frappe import _ from frappe.utils import getdate -from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries from erpnext.controllers.accounts_controller import AccountsController class Dunning(AccountsController): def validate(self): + self.validate_same_currency() self.validate_overdue_payments() self.validate_totals() + self.set_dunning_level() - if not self.income_account: - self.income_account = frappe.db.get_value("Company", self.company, "default_income_account") + def validate_same_currency(self): + """ + Throw an error if invoice currency differs from dunning currency. + """ + for row in self.overdue_payments: + invoice_currency = frappe.get_value("Sales Invoice", row.sales_invoice, "currency") + if invoice_currency != self.currency: + frappe.throw(_("The currency of invoice {} ({}) is different from the currency of this dunning ({}).").format(row.sales_invoice, invoice_currency, self.currency)) def validate_overdue_payments(self): daily_interest = self.rate_of_interest / 100 / 365 @@ -31,51 +51,25 @@ class Dunning(AccountsController): self.total_outstanding = sum(row.outstanding for row in self.overdue_payments) self.total_interest = sum(row.interest for row in self.overdue_payments) self.dunning_amount = self.total_interest + self.dunning_fee + self.base_dunning_amount = self.dunning_amount * self.conversion_rate self.grand_total = self.total_outstanding + self.dunning_amount - def on_submit(self): - self.make_gl_entries() - - def on_cancel(self): - if self.dunning_amount: - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry") - make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - - def make_gl_entries(self): - if not self.dunning_amount: - return - - cost_center = self.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - - make_gl_entries( - [ - self.get_gl_dict({ - "account": self.debit_to, - "party_type": "Customer", - "party": self.customer, - "due_date": self.due_date, - "against": self.income_account, - "debit": self.dunning_amount, - "debit_in_account_currency": self.dunning_amount, - "against_voucher": self.name, - "against_voucher_type": "Dunning", - "cost_center": cost_center - }), - self.get_gl_dict({ - "account": self.income_account, - "against": self.customer, - "credit": self.dunning_amount, - "cost_center": cost_center, - "credit_in_account_currency": self.dunning_amount - }) - ], - cancel=(self.docstatus == 2), - update_outstanding="No", - merge_entries=False - ) + def set_dunning_level(self): + for row in self.overdue_payments: + past_dunnings = frappe.get_all("Overdue Payment", + filters={ + "payment_schedule": row.payment_schedule, + "parent": ("!=", row.parent), + "docstatus": 1 + } + ) + row.dunning_level = len(past_dunnings) + 1 def resolve_dunning(doc, state): + """ + Todo: refactor + """ for reference in doc.references: if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0: dunnings = frappe.get_list( diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index b6d3e5a30e..397e998f0b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1849,30 +1849,20 @@ def get_payment_entry( pe.append("references", reference) else: if dt == "Dunning": - pe.append( - "references", - { + for overdue_payment in doc.overdue_payments: + pe.append("references", { "reference_doctype": "Sales Invoice", - "reference_name": doc.get("sales_invoice"), - "bill_no": doc.get("bill_no"), - "due_date": doc.get("due_date"), - "total_amount": doc.get("outstanding_amount"), - "outstanding_amount": doc.get("outstanding_amount"), - "allocated_amount": doc.get("outstanding_amount"), - }, - ) - pe.append( - "references", - { - "reference_doctype": dt, - "reference_name": dn, - "bill_no": doc.get("bill_no"), - "due_date": doc.get("due_date"), - "total_amount": doc.get("dunning_amount"), - "outstanding_amount": doc.get("dunning_amount"), - "allocated_amount": doc.get("dunning_amount"), - }, - ) + "reference_name": overdue_payment.sales_invoice, + "payment_term": overdue_payment.payment_term, + "due_date": overdue_payment.due_date, + "total_amount": overdue_payment.outstanding, + "outstanding_amount": overdue_payment.outstanding, + "allocated_amount": overdue_payment.outstanding + }) + + pe.append("deductions", { + "amount": doc.dunning_amount + }) else: pe.append( "references", From ff7ec977e6d75ff72d629ff3dabf5f6de0b2868f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 5 Oct 2021 18:06:13 +0200 Subject: [PATCH 024/107] feat: more info for payment deductions --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 397e998f0b..5793ecfe9a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1861,7 +1861,10 @@ def get_payment_entry( }) pe.append("deductions", { - "amount": doc.dunning_amount + "account": doc.income_account, + "cost_center": doc.cost_center, + "amount": doc.dunning_amount, + "description": _("Interest and/or dunning fee") }) else: pe.append( From 6b6f4dd017790ef47384c984f5ada4ae7c9634dd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 5 Oct 2021 18:18:23 +0200 Subject: [PATCH 025/107] refactor: run pre-commit --- erpnext/accounts/doctype/dunning/dunning.py | 1 - erpnext/accounts/doctype/dunning/test_dunning.py | 4 +++- erpnext/accounts/doctype/overdue_payment/overdue_payment.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index ec116f3061..81ec408344 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -16,7 +16,6 @@ from __future__ import unicode_literals import json import frappe - from frappe import _ from frappe.utils import getdate diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 956b1cfdbe..499a03b591 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -6,11 +6,13 @@ import unittest import frappe from frappe.utils import add_days, nowdate, today -from erpnext.accounts.doctype.sales_invoice.sales_invoice import create_dunning as create_dunning_from_sales_invoice from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( unlink_payment_on_cancel_of_invoice, ) +from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + create_dunning as create_dunning_from_sales_invoice, +) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( create_sales_invoice_against_cost_center, ) diff --git a/erpnext/accounts/doctype/overdue_payment/overdue_payment.py b/erpnext/accounts/doctype/overdue_payment/overdue_payment.py index e3820d74e0..6a543ad467 100644 --- a/erpnext/accounts/doctype/overdue_payment/overdue_payment.py +++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class OverduePayment(Document): pass From 270040303ce490a0156078927168616e1662e8ec Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 5 Oct 2021 18:24:03 +0200 Subject: [PATCH 026/107] refactor: make sider happy --- erpnext/accounts/doctype/dunning/dunning.js | 23 +++++++++------------ 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 5cee711950..e5a5e1f8a4 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -30,7 +30,7 @@ frappe.ui.form.on("Dunning", { "is_group": 0, "company": frm.doc.company } - } + }; }); frm.set_query("contact_person", erpnext.queries.contact_query); frm.set_query("customer_address", erpnext.queries.address_query); @@ -74,7 +74,7 @@ frappe.ui.form.on("Dunning", { }); } - frappe.dynamic_link = { doc: frm.doc, fieldname: 'customer', doctype: 'Customer' } + frappe.dynamic_link = { doc: frm.doc, fieldname: 'customer', doctype: 'Customer' }; frm.toggle_display("customer_name", (frm.doc.customer_name && frm.doc.customer_name !== frm.doc.customer)); }, @@ -87,10 +87,9 @@ frappe.ui.form.on("Dunning", { debounce: 2000, callback: function (r) { if (r.message) { - frm.set_value("company_address", r.message) - } - else { - frm.set_value("company_address", "") + frm.set_value("company_address", r.message); + } else { + frm.set_value("company_address", ""); } } }); @@ -141,11 +140,11 @@ frappe.ui.form.on("Dunning", { // this.set_dynamic_labels(); var company_currency = erpnext.get_currency(frm.doc.company); // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc - if(frm.doc.currency && frm.doc.currency !== company_currency) { + if (frm.doc.currency && frm.doc.currency !== company_currency) { frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { - transaction_date: transaction_date, + transaction_date: frm.doc.posting_date, from_currency: frm.doc.currency, to_currency: company_currency, args: "for_selling" @@ -154,7 +153,7 @@ frappe.ui.form.on("Dunning", { freeze_message: __("Fetching exchange rates ..."), callback: function(r) { const exchange_rate = flt(r.message); - if(exchange_rate != frm.doc.conversion_rate) { + if (exchange_rate != frm.doc.conversion_rate) { frm.set_value("conversion_rate", exchange_rate); } } @@ -164,7 +163,7 @@ frappe.ui.form.on("Dunning", { } }, conversion_rate: function (frm) { - if(frm.doc.currency === erpnext.get_currency(frm.doc.company)) { + if (frm.doc.currency === erpnext.get_currency(frm.doc.company)) { frm.set_value("conversion_rate", 1.0); } @@ -240,7 +239,6 @@ frappe.ui.form.on("Dunning", { }); }, calculate_totals: function (frm) { - debugger; const total_interest = frm.doc.overdue_payments .reduce((prev, cur) => prev + cur.interest, 0); const total_outstanding = frm.doc.overdue_payments @@ -276,8 +274,7 @@ frappe.ui.form.on("Dunning", { }); frappe.ui.form.on("Overdue Payment", { - interest: function (frm, cdt, cdn) { - debugger; + interest: function (frm) { frm.trigger("calculate_totals"); } }); \ No newline at end of file From ac8b6bba5ce8f8bddfe8094f1bb22b1c028a1d47 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 7 Oct 2021 13:04:09 +0200 Subject: [PATCH 027/107] feat: resolve dunning on payment entry --- erpnext/accounts/doctype/dunning/dunning.py | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 81ec408344..941a91df5f 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -67,18 +67,30 @@ class Dunning(AccountsController): def resolve_dunning(doc, state): """ - Todo: refactor + Check if all payments have been made and resolve dunning, if yes. Called + when a Payment Entry is submitted. """ for reference in doc.references: if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0: - dunnings = frappe.get_list( - "Dunning", - filters={"sales_invoice": reference.reference_name, "status": ("!=", "Resolved")}, - ignore_permissions=True, + unresolved_dunnings = frappe.get_all("Dunning", + filters={ + "sales_invoice": reference.reference_name, + "status": ("!=", "Resolved") + }, + pluck="name" ) - for dunning in dunnings: - frappe.db.set_value("Dunning", dunning.name, "status", "Resolved") + for dunning_name in unresolved_dunnings: + resolve = True + dunning = frappe.get_doc("Dunning", dunning_name) + for overdue_payment in dunning.overdue_payments: + outstanding = frappe.get_value("Payment Schedule", overdue_payment.payment_schedule, "outstanding") + if outstanding >= 0: + resolve = False + + if resolve: + dunning.status = "Resolved" + dunning.save() From c142d8995200e8e4d76ca36d1e4409e2d4abdd0d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 7 Oct 2021 13:08:52 +0200 Subject: [PATCH 028/107] tests: remove obsolete test --- .../accounts/doctype/dunning/test_dunning.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 499a03b591..f8acc6c025 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -47,26 +47,6 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.dunning_amount, 2), 10.41) self.assertEqual(round(dunning.grand_total, 2), 110.41) - def test_gl_entries(self): - dunning = create_second_dunning() - dunning.submit() - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s - order by account asc""", - dunning.name, - as_dict=1, - ) - self.assertTrue(gl_entries) - expected_values = dict((d[0], d) for d in [ - ['Debtors - _TC', 10.41, 0.0], - ['Sales - _TC', 0.0, 10.41] - ]) - for gle in gl_entries: - self.assertEqual(expected_values[gle.account][0], gle.account) - self.assertEqual(expected_values[gle.account][1], gle.debit) - self.assertEqual(expected_values[gle.account][2], gle.credit) - def test_payment_entry(self): dunning = create_second_dunning() dunning.submit() From fd7be5da99a134a8dec426fd25a1e3fc503cf77a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 7 Oct 2021 13:44:33 +0200 Subject: [PATCH 029/107] feat: remove obsolete "debit_to" field --- erpnext/accounts/doctype/dunning/dunning.js | 32 +------------------ erpnext/accounts/doctype/dunning/dunning.json | 9 ------ 2 files changed, 1 insertion(+), 40 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index e5a5e1f8a4..99b408a7a1 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -23,15 +23,7 @@ frappe.ui.form.on("Dunning", { } }; }); - frm.set_query("debit_to", () => { - return { - filters: { - "account_type": "Receivable", - "is_group": 0, - "company": frm.doc.company - } - }; - }); + frm.set_query("contact_person", erpnext.queries.contact_query); frm.set_query("customer_address", erpnext.queries.address_query); frm.set_query("company_address", erpnext.queries.company_address_query); @@ -113,28 +105,6 @@ frappe.ui.form.on("Dunning", { } } } - frm.trigger("set_debit_to"); - }, - set_debit_to: function(frm) { - if (frm.doc.customer && frm.doc.company) { - return frappe.call({ - method: "erpnext.accounts.party.get_party_account", - args: { - company: frm.doc.company, - party_type: "Customer", - party: frm.doc.customer, - currency: erpnext.get_currency(frm.doc.company) - }, - callback: function (r) { - if (!r.exc && r.message) { - frm.set_value("debit_to", r.message); - } - } - }); - } - }, - customer: function (frm) { - frm.trigger("set_debit_to"); }, currency: function (frm) { // this.set_dynamic_labels(); diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index 1dd05b77fa..fc2ccc7e5d 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -54,7 +54,6 @@ "accounting_details_section", "cost_center", "income_account", - "debit_to", "amended_from" ], "fields": [ @@ -349,14 +348,6 @@ "options": "Address", "print_hide": 1 }, - { - "fieldname": "debit_to", - "fieldtype": "Link", - "label": "Debit To", - "options": "Account", - "print_hide": 1, - "reqd": 1 - }, { "fieldname": "section_break_9", "fieldtype": "Section Break", From 0990011e743119f251b99714ce546b6a96a24b05 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 7 Oct 2021 19:05:35 +0200 Subject: [PATCH 030/107] feat: add patch for dunning --- erpnext/patches.txt | 1 + .../patches/v14_0/single_to_multi_dunning.py | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 erpnext/patches/v14_0/single_to_multi_dunning.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 18bd10f45f..03ef5de06e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -339,3 +339,4 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapper', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True) erpnext.patches.v14_0.cleanup_workspaces +erpnext.patches.v14_0.single_to_multi_dunning diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py new file mode 100644 index 0000000000..40fba041ef --- /dev/null +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -0,0 +1,46 @@ +import frappe +from erpnext.accounts.general_ledger import make_reverse_gl_entries + +def execute(): + frappe.reload_doc("accounts", "doctype", "overdue_payment") + frappe.reload_doc("accounts", "doctype", "dunning") + + all_dunnings = frappe.get_all("Dunning", pluck="name") + for dunning_name in all_dunnings: + dunning = frappe.get_doc("Dunning", dunning_name) + if not dunning.sales_invoice: + # nothing we can do + continue + + if dunning.overdue_payments: + # something's already here, doesn't need patching + continue + + payment_schedules = frappe.get_all("Payment Schedule", + filters={"parent": dunning.sales_invoice}, + fields=[ + "parent as sales_invoice", + "name as payment_schedule", + "payment_term", + "due_date", + "invoice_portion", + "payment_amount", + # at the time of creating this dunning, the full amount was outstanding + "payment_amount as outstanding", + "'0' as paid_amount", + "discounted_amount" + ] + ) + + dunning.extend("overdue_payments", payment_schedules) + dunning.validate() + + dunning.flags.ignore_validate_update_after_submit = True + dunning.save() + + if dunning.status != "Resolved": + # With the new logic, dunning amount gets recorded as additional income + # at time of payment. We don't want to record the dunning amount twice, + # so we reverse previous GL Entries that recorded the dunning amount at + # time of submission of the Dunning. + make_reverse_gl_entries(voucher_type="Dunning", voucher_no=dunning.name) From 1250e56dd6fb5132385fa4e6c74276b436a02f23 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:30:11 +0200 Subject: [PATCH 031/107] feat: add Dunning to Dunning Type's dashboard --- .../accounts/doctype/dunning_type/dunning_type.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json index ca33ce58a9..b80a8b6666 100644 --- a/erpnext/accounts/doctype/dunning_type/dunning_type.json +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json @@ -63,8 +63,14 @@ "label": "Is Default" } ], - "links": [], - "modified": "2021-09-16 15:00:02.610605", + "links": [ + { + "link_doctype": "Dunning", + "link_fieldname": "dunning_type" + } + ], + "migration_hash": "3a2c71ceb1a15469ffe1eca6053656a0", + "modified": "2021-10-12 17:26:48.080519", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning Type", From 24f400b12363e1804ea7e7dacfb522a849ccf247 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:30:46 +0200 Subject: [PATCH 032/107] feat: remove Dunning dashboard as there are no incoming links --- .../accounts/doctype/dunning/dunning_dashboard.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 erpnext/accounts/doctype/dunning/dunning_dashboard.py diff --git a/erpnext/accounts/doctype/dunning/dunning_dashboard.py b/erpnext/accounts/doctype/dunning/dunning_dashboard.py deleted file mode 100644 index d1d4031410..0000000000 --- a/erpnext/accounts/doctype/dunning/dunning_dashboard.py +++ /dev/null @@ -1,12 +0,0 @@ -from frappe import _ - - -def get_data(): - return { - "fieldname": "dunning", - "non_standard_fieldnames": { - "Journal Entry": "reference_name", - "Payment Entry": "reference_name", - }, - "transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]}], - } From c17ccb455d1507351ff89cf5d410a23d74901e27 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:48:54 +0200 Subject: [PATCH 033/107] refactor: run pre-commit --- erpnext/patches/v14_0/single_to_multi_dunning.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py index 40fba041ef..af83ef7096 100644 --- a/erpnext/patches/v14_0/single_to_multi_dunning.py +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -1,6 +1,8 @@ import frappe + from erpnext.accounts.general_ledger import make_reverse_gl_entries + def execute(): frappe.reload_doc("accounts", "doctype", "overdue_payment") frappe.reload_doc("accounts", "doctype", "dunning") @@ -39,7 +41,7 @@ def execute(): dunning.save() if dunning.status != "Resolved": - # With the new logic, dunning amount gets recorded as additional income + # With the new logic, dunning amount gets recorded as additional income # at time of payment. We don't want to record the dunning amount twice, # so we reverse previous GL Entries that recorded the dunning amount at # time of submission of the Dunning. From 9eeaac0c3efa480b1d45651f905756eb8a053b69 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 12 Oct 2021 18:35:02 +0200 Subject: [PATCH 034/107] feat: remove dunning as possible reference from payment entry --- .../doctype/payment_entry/payment_entry.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5793ecfe9a..5da89a39db 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -386,7 +386,7 @@ class PaymentEntry(AccountsController): def get_valid_reference_doctypes(self): if self.party_type == "Customer": - return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") + return ("Sales Order", "Sales Invoice", "Journal Entry") elif self.party_type == "Supplier": return ("Purchase Order", "Purchase Invoice", "Journal Entry") elif self.party_type == "Shareholder": @@ -1693,11 +1693,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre ref_doc.company ) - if reference_doctype == "Dunning": - total_amount = outstanding_amount = ref_doc.get("dunning_amount") - exchange_rate = 1 - - elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1: + if reference_doctype == "Journal Entry" and ref_doc.docstatus == 1: total_amount = ref_doc.get("total_amount") if ref_doc.multi_currency: exchange_rate = get_exchange_rate( @@ -1930,7 +1926,7 @@ def get_bank_cash_account(doc, bank_account): def set_party_type(dt): - if dt in ("Sales Invoice", "Sales Order", "Dunning"): + if dt in ("Sales Invoice", "Sales Order"): party_type = "Customer" elif dt in ("Purchase Invoice", "Purchase Order"): party_type = "Supplier" @@ -1957,7 +1953,7 @@ def set_party_account_currency(dt, party_account, doc): def set_payment_type(dt, doc): if ( - dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0) + dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0) ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0): payment_type = "Receive" else: @@ -1975,9 +1971,6 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre else: grand_total = doc.rounded_total or doc.grand_total outstanding_amount = doc.outstanding_amount - elif dt == "Dunning": - grand_total = doc.grand_total - outstanding_amount = doc.grand_total else: if party_account_currency == doc.company_currency: grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total")) From 8652331d1c8c9a5f3d3d923b9be1b9e8ea1d5afe Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Oct 2021 19:10:13 +0200 Subject: [PATCH 035/107] Revert "feat: remove dunning as possible reference from payment entry" This reverts commit b774d8d0e3c1e5a53b3422591b3f2d52ca959645. --- .../doctype/payment_entry/payment_entry.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5da89a39db..5793ecfe9a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -386,7 +386,7 @@ class PaymentEntry(AccountsController): def get_valid_reference_doctypes(self): if self.party_type == "Customer": - return ("Sales Order", "Sales Invoice", "Journal Entry") + return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") elif self.party_type == "Supplier": return ("Purchase Order", "Purchase Invoice", "Journal Entry") elif self.party_type == "Shareholder": @@ -1693,7 +1693,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre ref_doc.company ) - if reference_doctype == "Journal Entry" and ref_doc.docstatus == 1: + if reference_doctype == "Dunning": + total_amount = outstanding_amount = ref_doc.get("dunning_amount") + exchange_rate = 1 + + elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1: total_amount = ref_doc.get("total_amount") if ref_doc.multi_currency: exchange_rate = get_exchange_rate( @@ -1926,7 +1930,7 @@ def get_bank_cash_account(doc, bank_account): def set_party_type(dt): - if dt in ("Sales Invoice", "Sales Order"): + if dt in ("Sales Invoice", "Sales Order", "Dunning"): party_type = "Customer" elif dt in ("Purchase Invoice", "Purchase Order"): party_type = "Supplier" @@ -1953,7 +1957,7 @@ def set_party_account_currency(dt, party_account, doc): def set_payment_type(dt, doc): if ( - dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0) + dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0) ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0): payment_type = "Receive" else: @@ -1971,6 +1975,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre else: grand_total = doc.rounded_total or doc.grand_total outstanding_amount = doc.outstanding_amount + elif dt == "Dunning": + grand_total = doc.grand_total + outstanding_amount = doc.grand_total else: if party_account_currency == doc.company_currency: grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total")) From e37f98267bb4691ef108fa81546335e741f56639 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:53:26 +0200 Subject: [PATCH 036/107] fix: resolve dunning --- erpnext/accounts/doctype/dunning/dunning.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 941a91df5f..0a55ff5f5f 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -84,8 +84,9 @@ def resolve_dunning(doc, state): resolve = True dunning = frappe.get_doc("Dunning", dunning_name) for overdue_payment in dunning.overdue_payments: - outstanding = frappe.get_value("Payment Schedule", overdue_payment.payment_schedule, "outstanding") - if outstanding >= 0: + outstanding_inv = frappe.get_value("Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount") + outstanding_ps = frappe.get_value("Payment Schedule", overdue_payment.payment_schedule, "outstanding") + if outstanding_ps > 0 and outstanding_inv > 0: resolve = False if resolve: From 84459c719662e0cd04255f5d8273e65eb6c6bb5b Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:55:22 +0200 Subject: [PATCH 037/107] fix: create payment entry --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5793ecfe9a..090308f6fd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1863,7 +1863,7 @@ def get_payment_entry( pe.append("deductions", { "account": doc.income_account, "cost_center": doc.cost_center, - "amount": doc.dunning_amount, + "amount": -1 * doc.dunning_amount, "description": _("Interest and/or dunning fee") }) else: @@ -1957,8 +1957,8 @@ def set_party_account_currency(dt, party_account, doc): def set_payment_type(dt, doc): if ( - dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0) - ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0): + dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0) + ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0) or dt == "Dunning": payment_type = "Receive" else: payment_type = "Pay" From d55c59f2985e8ef5dbaca91fc67e89af1112efe9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:57:23 +0200 Subject: [PATCH 038/107] test: make failing tests work --- .../accounts/doctype/dunning/test_dunning.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index f8acc6c025..b114fcec39 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -53,38 +53,43 @@ class TestDunning(unittest.TestCase): pe = get_payment_entry("Dunning", dunning.name) pe.reference_no = "1" pe.reference_date = nowdate() - pe.paid_from_account_currency = dunning.currency - pe.paid_to_account_currency = dunning.currency - pe.source_exchange_rate = 1 - pe.target_exchange_rate = 1 pe.insert() pe.submit() - si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice) - self.assertEqual(si_doc.outstanding_amount, 0) + + for overdue_payment in dunning.overdue_payments: + outstanding_amount = frappe.get_value( + "Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount" + ) + self.assertEqual(outstanding_amount, 0) + + dunning.reload() + self.assertEqual(dunning.status, "Resolved") def create_first_dunning(): posting_date = add_days(today(), -20) - due_date = add_days(today(), -15) sales_invoice = create_sales_invoice_against_cost_center( - posting_date=posting_date, due_date=due_date, qty=1, rate=100) + posting_date=posting_date, qty=1, rate=100 + ) dunning = create_dunning_from_sales_invoice(sales_invoice.name) + dunning.income_account = "Interest Income Account - _TC" dunning.save() return dunning def create_second_dunning(): - posting_date = add_days(today(), -20) - due_date = add_days(today(), -15) + posting_date = add_days(today(), -15) sales_invoice = create_sales_invoice_against_cost_center( - posting_date=posting_date, due_date=due_date, qty=1, rate=100) + posting_date=posting_date, qty=1, rate=100 + ) dunning = create_dunning_from_sales_invoice(sales_invoice.name) dunning_type = frappe.get_doc("Dunning Type", "Second Notice") dunning.dunning_type = dunning_type.name dunning.rate_of_interest = dunning_type.rate_of_interest dunning.dunning_fee = dunning_type.dunning_fee + dunning.income_account = "Interest Income Account - _TC" dunning.save() return dunning @@ -101,11 +106,12 @@ def create_dunning_type(title, fee, interest, is_default): dunning_type.dunning_fee = fee dunning_type.rate_of_interest = interest dunning_type.append( - "dunning_letter_text", { + "dunning_letter_text", + { "language": "en", "body_text": "We have still not received payment for our invoice", - "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees." - } + "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.", + }, ) dunning_type.save() return dunning_type From 0a06241e7c005f9a595f5a93b5f69dbe58cc54ab Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 22 Oct 2021 12:05:45 +0200 Subject: [PATCH 039/107] test: refactor, fix missing income account --- .../accounts/doctype/dunning/test_dunning.py | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index b114fcec39..4048f2a846 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -30,7 +30,7 @@ class TestDunning(unittest.TestCase): unlink_payment_on_cancel_of_invoice(0) def test_first_dunning(self): - dunning = create_first_dunning() + dunning = create_dunning(overdue_days=20) self.assertEqual(round(dunning.total_outstanding, 2), 100.00) self.assertEqual(round(dunning.total_interest, 2), 0.00) @@ -39,7 +39,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.grand_total, 2), 100.00) def test_second_dunning(self): - dunning = create_second_dunning() + dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice") self.assertEqual(round(dunning.total_outstanding, 2), 100.00) self.assertEqual(round(dunning.total_interest, 2), 0.41) @@ -48,7 +48,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.grand_total, 2), 110.41) def test_payment_entry(self): - dunning = create_second_dunning() + dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice") dunning.submit() pe = get_payment_entry("Dunning", dunning.name) pe.reference_no = "1" @@ -66,30 +66,20 @@ class TestDunning(unittest.TestCase): self.assertEqual(dunning.status, "Resolved") -def create_first_dunning(): - posting_date = add_days(today(), -20) +def create_dunning(overdue_days, dunning_type_name=None): + posting_date = add_days(today(), -1 * overdue_days) sales_invoice = create_sales_invoice_against_cost_center( posting_date=posting_date, qty=1, rate=100 ) dunning = create_dunning_from_sales_invoice(sales_invoice.name) - dunning.income_account = "Interest Income Account - _TC" - dunning.save() - return dunning + if dunning_type_name: + dunning_type = frappe.get_doc("Dunning Type", dunning_type_name) + dunning.dunning_type = dunning_type.name + dunning.rate_of_interest = dunning_type.rate_of_interest + dunning.dunning_fee = dunning_type.dunning_fee - -def create_second_dunning(): - posting_date = add_days(today(), -15) - sales_invoice = create_sales_invoice_against_cost_center( - posting_date=posting_date, qty=1, rate=100 - ) - dunning = create_dunning_from_sales_invoice(sales_invoice.name) - dunning_type = frappe.get_doc("Dunning Type", "Second Notice") - - dunning.dunning_type = dunning_type.name - dunning.rate_of_interest = dunning_type.rate_of_interest - dunning.dunning_fee = dunning_type.dunning_fee - dunning.income_account = "Interest Income Account - _TC" + dunning.income_account = get_income_account(dunning.company) dunning.save() return dunning @@ -115,3 +105,16 @@ def create_dunning_type(title, fee, interest, is_default): ) dunning_type.save() return dunning_type + + +def get_income_account(company): + return frappe.get_value("Company", company, "default_income_account") or frappe.get_all( + "Account", + filters={"is_group": 0, "company": company}, + or_filters={ + "report_type": "Profit and Loss", + "account_type": ("in", ("Income Account", "Temporary")), + }, + limit=1, + pluck="name", + )[0] From 8bfe8657596168d9ebf921bbbd21b7a6d81fa37f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 12 Nov 2021 23:24:08 +0100 Subject: [PATCH 040/107] fix: ignore cancelled dunnings --- erpnext/accounts/doctype/dunning/dunning.py | 3 ++- erpnext/patches/v14_0/single_to_multi_dunning.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 0a55ff5f5f..719f3698dc 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -75,7 +75,8 @@ def resolve_dunning(doc, state): unresolved_dunnings = frappe.get_all("Dunning", filters={ "sales_invoice": reference.reference_name, - "status": ("!=", "Resolved") + "status": ("!=", "Resolved"), + "docstatus": ("!=", 2), }, pluck="name" ) diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py index af83ef7096..90966aa4cb 100644 --- a/erpnext/patches/v14_0/single_to_multi_dunning.py +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -7,7 +7,7 @@ def execute(): frappe.reload_doc("accounts", "doctype", "overdue_payment") frappe.reload_doc("accounts", "doctype", "dunning") - all_dunnings = frappe.get_all("Dunning", pluck="name") + all_dunnings = frappe.get_all("Dunning", filters={"docstatus": ("!=", 2)}, pluck="name") for dunning_name in all_dunnings: dunning = frappe.get_doc("Dunning", dunning_name) if not dunning.sales_invoice: From 60b6afb470bd750d6cbac0e04a5f39c312a27765 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 13 Nov 2021 01:39:22 +0100 Subject: [PATCH 041/107] fix: fetch overdue payments --- erpnext/accounts/doctype/dunning/dunning.js | 6 +++++- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 99b408a7a1..c2b91690dc 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -49,10 +49,11 @@ frappe.ui.form.on("Dunning", { } if (frm.doc.docstatus === 0) { - frm.add_custom_button(__("Fetch Overdue Payments"), function () { + frm.add_custom_button(__("Fetch Overdue Payments"), () => { erpnext.utils.map_current_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning", source_doctype: "Sales Invoice", + date_field: "due_date", target: frm, setters: { customer: frm.doc.customer || undefined, @@ -62,6 +63,9 @@ frappe.ui.form.on("Dunning", { status: "Overdue", company: frm.doc.company }, + allow_child_item_selection: true, + child_fielname: "payment_schedule", + child_columns: ["due_date", "outstanding"], }); }); } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f8f7c3666a..3cce388e92 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2516,7 +2516,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): def postprocess_dunning(source, target): from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text - dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1}) + dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1, "company": source.company}) if dunning_type: dunning_type = frappe.get_doc("Dunning Type", dunning_type) target.dunning_type = dunning_type.name @@ -2538,6 +2538,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): return get_mapped_doc( from_doctype="Sales Invoice", from_docname=source_name, + target_doc=target_doc, table_maps={ "Sales Invoice": { "doctype": "Dunning", From 28dfbdda9375603bf53224c435535784d9e0fe13 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 13 Nov 2021 01:42:06 +0100 Subject: [PATCH 042/107] feat: fetch income account and cost center from dunning type --- erpnext/accounts/doctype/dunning/dunning.js | 8 ++++ erpnext/accounts/doctype/dunning/dunning.json | 10 +++- .../doctype/dunning_type/dunning_type.js | 24 ++++++++-- .../doctype/dunning_type/dunning_type.json | 47 +++++++++++++++++-- .../doctype/dunning_type/dunning_type.py | 6 ++- 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index c2b91690dc..03553f775c 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -23,6 +23,14 @@ frappe.ui.form.on("Dunning", { } }; }); + frm.set_query("cost_center", () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); frm.set_query("contact_person", erpnext.queries.contact_query); frm.set_query("customer_address", erpnext.queries.address_query); diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index fc2ccc7e5d..20e843c922 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -2,6 +2,7 @@ "actions": [], "allow_events_in_timeline": 1, "autoname": "naming_series:", + "beta": 1, "creation": "2019-07-05 16:34:31.013238", "doctype": "DocType", "engine": "InnoDB", @@ -52,8 +53,9 @@ "letter_head", "closing_text", "accounting_details_section", - "cost_center", "income_account", + "column_break_48", + "cost_center", "amended_from" ], "fields": [ @@ -247,6 +249,7 @@ }, { "description": "For dunning fee and interest", + "fetch_from": "dunning_type.income_account", "fieldname": "income_account", "fieldtype": "Link", "label": "Income Account", @@ -308,6 +311,7 @@ "label": "Accounting Details" }, { + "fetch_from": "dunning_type.cost_center", "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", @@ -375,6 +379,10 @@ "label": "Dunning Amount (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "column_break_48", + "fieldtype": "Column Break" } ], "is_submittable": 1, diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.js b/erpnext/accounts/doctype/dunning_type/dunning_type.js index 54156b488d..b2c08c1c7f 100644 --- a/erpnext/accounts/doctype/dunning_type/dunning_type.js +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.js @@ -1,8 +1,24 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Dunning Type', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Dunning Type", { + setup: function (frm) { + frm.set_query("income_account", () => { + return { + filters: { + root_type: "Income", + is_group: 0, + company: frm.doc.company, + }, + }; + }); + frm.set_query("cost_center", () => { + return { + filters: { + is_group: 0, + company: frm.doc.company, + }, + }; + }); + }, }); diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json index b80a8b6666..5e39769735 100644 --- a/erpnext/accounts/doctype/dunning_type/dunning_type.json +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json @@ -1,7 +1,7 @@ { "actions": [], "allow_rename": 1, - "autoname": "field:dunning_type", + "beta": 1, "creation": "2019-12-04 04:59:08.003664", "doctype": "DocType", "editable_grid": 1, @@ -9,12 +9,18 @@ "field_order": [ "dunning_type", "is_default", + "column_break_3", + "company", "section_break_6", "dunning_fee", "column_break_8", "rate_of_interest", "text_block_section", - "dunning_letter_text" + "dunning_letter_text", + "section_break_9", + "income_account", + "column_break_13", + "cost_center" ], "fields": [ { @@ -61,6 +67,38 @@ "fieldname": "is_default", "fieldtype": "Check", "label": "Is Default" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" } ], "links": [ @@ -69,12 +107,11 @@ "link_fieldname": "dunning_type" } ], - "migration_hash": "3a2c71ceb1a15469ffe1eca6053656a0", - "modified": "2021-10-12 17:26:48.080519", + "modified": "2021-11-13 00:25:35.659283", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning Type", - "naming_rule": "By fieldname", + "naming_rule": "By script", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.py b/erpnext/accounts/doctype/dunning_type/dunning_type.py index 1b9bb9c032..b053eb51d6 100644 --- a/erpnext/accounts/doctype/dunning_type/dunning_type.py +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.py @@ -2,9 +2,11 @@ # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document class DunningType(Document): - pass + def autoname(self): + company_abbr = frappe.get_value("Company", self.company, "abbr") + self.name = self.dunning_type + " - " + company_abbr From d790710ae73f3ff9c52141c02f645646daf07f6e Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 29 Nov 2021 12:11:30 +0100 Subject: [PATCH 043/107] refactor: apply suggestions from code review Co-authored-by: Himanshu --- erpnext/accounts/doctype/dunning/dunning.js | 12 ++++-------- .../accounts/doctype/dunning_type/dunning_type.py | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 03553f775c..8930fcc6cb 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -90,16 +90,12 @@ frappe.ui.form.on("Dunning", { args: { name: frm.doc.company, existing_address: frm.doc.company_address || "" }, debounce: 2000, callback: function (r) { - if (r.message) { - frm.set_value("company_address", r.message); - } else { - frm.set_value("company_address", ""); - } + frm.set_value("company_address", r && r.message || ""); } }); if (frm.fields_dict.currency) { - var company_currency = erpnext.get_currency(frm.doc.company); + const company_currency = erpnext.get_currency(frm.doc.company); if (!frm.doc.currency) { frm.set_value("currency", company_currency); @@ -110,7 +106,7 @@ frappe.ui.form.on("Dunning", { } } - var company_doc = frappe.get_doc(":Company", frm.doc.company); + const company_doc = frappe.get_doc(":Company", frm.doc.company); if (company_doc.default_letter_head) { if (frm.fields_dict.letter_head) { frm.set_value("letter_head", company_doc.default_letter_head); @@ -120,7 +116,7 @@ frappe.ui.form.on("Dunning", { }, currency: function (frm) { // this.set_dynamic_labels(); - var company_currency = erpnext.get_currency(frm.doc.company); + const company_currency = erpnext.get_currency(frm.doc.company); // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc if (frm.doc.currency && frm.doc.currency !== company_currency) { frappe.call({ diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.py b/erpnext/accounts/doctype/dunning_type/dunning_type.py index b053eb51d6..226e159a3b 100644 --- a/erpnext/accounts/doctype/dunning_type/dunning_type.py +++ b/erpnext/accounts/doctype/dunning_type/dunning_type.py @@ -9,4 +9,4 @@ from frappe.model.document import Document class DunningType(Document): def autoname(self): company_abbr = frappe.get_value("Company", self.company, "abbr") - self.name = self.dunning_type + " - " + company_abbr + self.name = f"{self.dunning_type} - {company_abbr}" From 028d19f32dca25dc8fb111082f82482d082eec18 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 28 Dec 2021 19:31:18 +0100 Subject: [PATCH 044/107] test: link Dunning Type to COmpany --- erpnext/accounts/doctype/dunning/test_dunning.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 4048f2a846..925b7e5e55 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -92,6 +92,7 @@ def create_dunning_type(title, fee, interest, is_default): dunning_type = frappe.new_doc("Dunning Type") dunning_type.dunning_type = title + dunning_type.company = "_Test Company" dunning_type.is_default = is_default dunning_type.dunning_fee = fee dunning_type.rate_of_interest = interest From fd6d86eefc37d9447fb0c32cc13fa94a75e963ab Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 28 Dec 2021 23:50:05 +0100 Subject: [PATCH 045/107] fix: show "Create Dunning" when any scheduled payment is overdue --- .../accounts/doctype/sales_invoice/sales_invoice.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8cb29505eb..6b0c2ee76f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -138,8 +138,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e cur_frm.events.create_invoice_discounting(cur_frm); }, __('Create')); - if (doc.due_date < frappe.datetime.get_today()) { - cur_frm.add_custom_button(__('Dunning'), function() { + const payment_is_overdue = doc.payment_schedule.map( + row => Date.parse(row.due_date) < Date.now() + ).reduce( + (prev, current) => prev || current + ); + + if (payment_is_overdue) { + cur_frm.add_custom_button(__('Dunning'), function () { cur_frm.events.create_dunning(cur_frm); }, __('Create')); } From 88f67e47862883a1084d137a8a150d9dcf0ad9e7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 28 Dec 2021 23:51:32 +0100 Subject: [PATCH 046/107] fix: set income account and cost center --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3cce388e92..d1494b7f7c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2522,6 +2522,8 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.dunning_type = dunning_type.name target.rate_of_interest = dunning_type.rate_of_interest target.dunning_fee = dunning_type.dunning_fee + target.income_account = dunning_type.income_account + target.cost_center = dunning_type.cost_center letter_text = get_dunning_letter_text( dunning_type=dunning_type.name, doc=target.as_dict(), From ccefe96665b651794fec79ce2ba4251563dd9cfc Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 29 Dec 2021 00:09:52 +0100 Subject: [PATCH 047/107] fix: map only overdue payments --- .../doctype/sales_invoice/sales_invoice.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index d1494b7f7c..7b741495ea 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -622,9 +622,7 @@ class SalesInvoice(SellingController): return if not self.account_for_change_amount: - self.account_for_change_amount = frappe.get_cached_value( - "Company", self.company, "default_cash_account" - ) + self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details @@ -1909,17 +1907,17 @@ def get_bank_cash_account(mode_of_payment, company): @frappe.whitelist() def make_maintenance_schedule(source_name, target_doc=None): - doclist = get_mapped_doc( - "Sales Invoice", - source_name, - { - "Sales Invoice": {"doctype": "Maintenance Schedule", "validation": {"docstatus": ["=", 1]}}, - "Sales Invoice Item": { - "doctype": "Maintenance Schedule Item", - }, + doclist = get_mapped_doc("Sales Invoice", source_name, { + "Sales Invoice": { + "doctype": "Maintenance Schedule", + "validation": { + "docstatus": ["=", 1] + } }, - target_doc, - ) + "Sales Invoice Item": { + "doctype": "Maintenance Schedule Item", + }, + }, target_doc) return doclist @@ -2555,7 +2553,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): "name": "payment_schedule", "parent": "sales_invoice" }, - "condition": lambda doc: doc.outstanding > 0 + "condition": lambda doc: doc.outstanding > 0 and getdate(doc.due_date) < getdate(), } }, postprocess=postprocess_dunning, From 4911c3b5b74f10115237528512154f5fd1d96053 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 29 Dec 2021 00:13:23 +0100 Subject: [PATCH 048/107] fix: precision for interst --- erpnext/accounts/doctype/dunning/dunning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 8930fcc6cb..a99b44ff1e 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -212,7 +212,7 @@ frappe.ui.form.on("Dunning", { calculate_interest: function (frm) { frm.doc.overdue_payments.forEach((row) => { const interest_per_day = frm.doc.rate_of_interest / 100 / 365; - const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest")); + const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest", row)); frappe.model.set_value(row.doctype, row.name, "interest", interest); }); }, From 04aaadcb3951453c488a662040b58e79e98e840e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 3 Jan 2022 11:27:47 +0100 Subject: [PATCH 049/107] style: sider issues --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7b741495ea..b2cd4a6d08 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -622,7 +622,7 @@ class SalesInvoice(SellingController): return if not self.account_for_change_amount: - self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') + self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details @@ -1907,7 +1907,7 @@ def get_bank_cash_account(mode_of_payment, company): @frappe.whitelist() def make_maintenance_schedule(source_name, target_doc=None): - doclist = get_mapped_doc("Sales Invoice", source_name, { + doclist = get_mapped_doc("Sales Invoice", source_name, { "Sales Invoice": { "doctype": "Maintenance Schedule", "validation": { From 315df7b2cf6261fb4656a8634026937d0e1007d8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 3 Jan 2022 12:46:46 +0100 Subject: [PATCH 050/107] test: fix dunning test --- .../accounts/doctype/dunning/test_dunning.py | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 925b7e5e55..129ca32d3a 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -6,6 +6,7 @@ import unittest import frappe from frappe.utils import add_days, nowdate, today +from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( unlink_payment_on_cancel_of_invoice, @@ -17,16 +18,19 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( create_sales_invoice_against_cost_center, ) +test_dependencies = ["Company", "Cost Center"] + class TestDunning(unittest.TestCase): @classmethod - def setUpClass(self): + def setUpClass(cls): create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1) create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0) unlink_payment_on_cancel_of_invoice() + frappe.db.commit() @classmethod - def tearDownClass(self): + def tearDownClass(cls): unlink_payment_on_cancel_of_invoice(0) def test_first_dunning(self): @@ -39,7 +43,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.grand_total, 2), 100.00) def test_second_dunning(self): - dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice") + dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC") self.assertEqual(round(dunning.total_outstanding, 2), 100.00) self.assertEqual(round(dunning.total_interest, 2), 0.41) @@ -48,7 +52,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.grand_total, 2), 110.41) def test_payment_entry(self): - dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice") + dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC") dunning.submit() pe = get_payment_entry("Dunning", dunning.name) pe.reference_no = "1" @@ -78,24 +82,25 @@ def create_dunning(overdue_days, dunning_type_name=None): dunning.dunning_type = dunning_type.name dunning.rate_of_interest = dunning_type.rate_of_interest dunning.dunning_fee = dunning_type.dunning_fee + dunning.income_account = dunning_type.income_account + dunning.cost_center = dunning_type.cost_center - dunning.income_account = get_income_account(dunning.company) - dunning.save() - - return dunning + return dunning.save() def create_dunning_type(title, fee, interest, is_default): - existing = frappe.db.exists("Dunning Type", title) - if existing: - return frappe.get_doc("Dunning Type", existing) + company = "_Test Company" + if frappe.db.exists("Dunning Type", f"{title} - _TC"): + return dunning_type = frappe.new_doc("Dunning Type") dunning_type.dunning_type = title - dunning_type.company = "_Test Company" + dunning_type.company = company dunning_type.is_default = is_default dunning_type.dunning_fee = fee dunning_type.rate_of_interest = interest + dunning_type.income_account = get_income_account(company) + dunning_type.cost_center = get_default_cost_center(company) dunning_type.append( "dunning_letter_text", { @@ -104,8 +109,7 @@ def create_dunning_type(title, fee, interest, is_default): "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.", }, ) - dunning_type.save() - return dunning_type + dunning_type.insert() def get_income_account(company): From 15816c8afd0ec35adb5eaf4fad07b0c43db3713f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 3 Jan 2022 12:47:29 +0100 Subject: [PATCH 051/107] test: test records for dunning type --- .../doctype/dunning_type/test_records.json | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 erpnext/accounts/doctype/dunning_type/test_records.json diff --git a/erpnext/accounts/doctype/dunning_type/test_records.json b/erpnext/accounts/doctype/dunning_type/test_records.json new file mode 100644 index 0000000000..cb589bf9ca --- /dev/null +++ b/erpnext/accounts/doctype/dunning_type/test_records.json @@ -0,0 +1,36 @@ +[ + { + "doctype": "Dunning Type", + "dunning_type": "_Test First Notice", + "company": "_Test Company", + "is_default": 1, + "dunning_fee": 0.0, + "rate_of_interest": 0.0, + "dunning_letter_text": [ + { + "language": "en", + "body_text": "We have still not received payment for our invoice", + "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees." + } + ], + "income_account": "Sales - _TC", + "cost_center": "_Test Cost Center" + }, + { + "doctype": "Dunning Type", + "dunning_type": "_Test Second Notice", + "company": "_Test Company", + "is_default": 0, + "dunning_fee": 10.0, + "rate_of_interest": 10.0, + "dunning_letter_text": [ + { + "language": "en", + "body_text": "We have still not received payment for our invoice", + "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees." + } + ], + "income_account": "Sales - _TC", + "cost_center": "_Test Cost Center" + } +] From 18495ed624c86d9f4cd0a75a87bedb70a0e74a04 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 3 Jan 2022 13:20:50 +0100 Subject: [PATCH 052/107] fix: semgrep issues --- erpnext/accounts/doctype/dunning/test_dunning.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 129ca32d3a..6125bd26c6 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -27,7 +27,6 @@ class TestDunning(unittest.TestCase): create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1) create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0) unlink_payment_on_cancel_of_invoice() - frappe.db.commit() @classmethod def tearDownClass(cls): From 772f6ffd212d564df1fa3b6f858e642ed9eb0d5b Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 14 Jun 2023 16:48:18 +0530 Subject: [PATCH 053/107] fix: Linter and incorrect cost center in test records --- erpnext/accounts/doctype/dunning/dunning.py | 28 +++++++----- .../accounts/doctype/dunning/test_dunning.py | 23 +++++----- .../doctype/dunning_type/test_records.json | 4 +- .../doctype/payment_entry/payment_entry.py | 42 ++++++++++-------- .../doctype/sales_invoice/sales_invoice.py | 43 ++++++++----------- .../patches/v14_0/single_to_multi_dunning.py | 7 +-- 6 files changed, 80 insertions(+), 67 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 719f3698dc..e0d75d3b47 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -23,7 +23,6 @@ from erpnext.controllers.accounts_controller import AccountsController class Dunning(AccountsController): - def validate(self): self.validate_same_currency() self.validate_overdue_payments() @@ -37,7 +36,11 @@ class Dunning(AccountsController): for row in self.overdue_payments: invoice_currency = frappe.get_value("Sales Invoice", row.sales_invoice, "currency") if invoice_currency != self.currency: - frappe.throw(_("The currency of invoice {} ({}) is different from the currency of this dunning ({}).").format(row.sales_invoice, invoice_currency, self.currency)) + frappe.throw( + _( + "The currency of invoice {} ({}) is different from the currency of this dunning ({})." + ).format(row.sales_invoice, invoice_currency, self.currency) + ) def validate_overdue_payments(self): daily_interest = self.rate_of_interest / 100 / 365 @@ -55,12 +58,13 @@ class Dunning(AccountsController): def set_dunning_level(self): for row in self.overdue_payments: - past_dunnings = frappe.get_all("Overdue Payment", + past_dunnings = frappe.get_all( + "Overdue Payment", filters={ "payment_schedule": row.payment_schedule, "parent": ("!=", row.parent), - "docstatus": 1 - } + "docstatus": 1, + }, ) row.dunning_level = len(past_dunnings) + 1 @@ -72,21 +76,26 @@ def resolve_dunning(doc, state): """ for reference in doc.references: if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0: - unresolved_dunnings = frappe.get_all("Dunning", + unresolved_dunnings = frappe.get_all( + "Dunning", filters={ "sales_invoice": reference.reference_name, "status": ("!=", "Resolved"), "docstatus": ("!=", 2), }, - pluck="name" + pluck="name", ) for dunning_name in unresolved_dunnings: resolve = True dunning = frappe.get_doc("Dunning", dunning_name) for overdue_payment in dunning.overdue_payments: - outstanding_inv = frappe.get_value("Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount") - outstanding_ps = frappe.get_value("Payment Schedule", overdue_payment.payment_schedule, "outstanding") + outstanding_inv = frappe.get_value( + "Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount" + ) + outstanding_ps = frappe.get_value( + "Payment Schedule", overdue_payment.payment_schedule, "outstanding" + ) if outstanding_ps > 0 and outstanding_inv > 0: resolve = False @@ -95,7 +104,6 @@ def resolve_dunning(doc, state): dunning.save() - @frappe.whitelist() def get_dunning_letter_text(dunning_type, doc, language=None): if isinstance(doc, str): diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 6125bd26c6..be8c533d8d 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -112,13 +112,16 @@ def create_dunning_type(title, fee, interest, is_default): def get_income_account(company): - return frappe.get_value("Company", company, "default_income_account") or frappe.get_all( - "Account", - filters={"is_group": 0, "company": company}, - or_filters={ - "report_type": "Profit and Loss", - "account_type": ("in", ("Income Account", "Temporary")), - }, - limit=1, - pluck="name", - )[0] + return ( + frappe.get_value("Company", company, "default_income_account") + or frappe.get_all( + "Account", + filters={"is_group": 0, "company": company}, + or_filters={ + "report_type": "Profit and Loss", + "account_type": ("in", ("Income Account", "Temporary")), + }, + limit=1, + pluck="name", + )[0] + ) diff --git a/erpnext/accounts/doctype/dunning_type/test_records.json b/erpnext/accounts/doctype/dunning_type/test_records.json index cb589bf9ca..7f28aab873 100644 --- a/erpnext/accounts/doctype/dunning_type/test_records.json +++ b/erpnext/accounts/doctype/dunning_type/test_records.json @@ -14,7 +14,7 @@ } ], "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center" + "cost_center": "_Test Cost Center - _TC" }, { "doctype": "Dunning Type", @@ -31,6 +31,6 @@ } ], "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center" + "cost_center": "_Test Cost Center - _TC" } ] diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 090308f6fd..2bd703f4bc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1850,22 +1850,28 @@ def get_payment_entry( else: if dt == "Dunning": for overdue_payment in doc.overdue_payments: - pe.append("references", { - "reference_doctype": "Sales Invoice", - "reference_name": overdue_payment.sales_invoice, - "payment_term": overdue_payment.payment_term, - "due_date": overdue_payment.due_date, - "total_amount": overdue_payment.outstanding, - "outstanding_amount": overdue_payment.outstanding, - "allocated_amount": overdue_payment.outstanding - }) + pe.append( + "references", + { + "reference_doctype": "Sales Invoice", + "reference_name": overdue_payment.sales_invoice, + "payment_term": overdue_payment.payment_term, + "due_date": overdue_payment.due_date, + "total_amount": overdue_payment.outstanding, + "outstanding_amount": overdue_payment.outstanding, + "allocated_amount": overdue_payment.outstanding, + }, + ) - pe.append("deductions", { - "account": doc.income_account, - "cost_center": doc.cost_center, - "amount": -1 * doc.dunning_amount, - "description": _("Interest and/or dunning fee") - }) + pe.append( + "deductions", + { + "account": doc.income_account, + "cost_center": doc.cost_center, + "amount": -1 * doc.dunning_amount, + "description": _("Interest and/or dunning fee"), + }, + ) else: pe.append( "references", @@ -1957,8 +1963,10 @@ def set_party_account_currency(dt, party_account, doc): def set_payment_type(dt, doc): if ( - dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0) - ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0) or dt == "Dunning": + (dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0)) + or (dt == "Purchase Invoice" and doc.outstanding_amount < 0) + or dt == "Dunning" + ): payment_type = "Receive" else: payment_type = "Pay" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b2cd4a6d08..e3a159ba58 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -622,7 +622,9 @@ class SalesInvoice(SellingController): return if not self.account_for_change_amount: - self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') + self.account_for_change_amount = frappe.get_cached_value( + "Company", self.company, "default_cash_account" + ) from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details @@ -1907,17 +1909,17 @@ def get_bank_cash_account(mode_of_payment, company): @frappe.whitelist() def make_maintenance_schedule(source_name, target_doc=None): - doclist = get_mapped_doc("Sales Invoice", source_name, { - "Sales Invoice": { - "doctype": "Maintenance Schedule", - "validation": { - "docstatus": ["=", 1] - } + doclist = get_mapped_doc( + "Sales Invoice", + source_name, + { + "Sales Invoice": {"doctype": "Maintenance Schedule", "validation": {"docstatus": ["=", 1]}}, + "Sales Invoice Item": { + "doctype": "Maintenance Schedule Item", + }, }, - "Sales Invoice Item": { - "doctype": "Maintenance Schedule Item", - }, - }, target_doc) + target_doc, + ) return doclist @@ -2523,9 +2525,7 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.income_account = dunning_type.income_account target.cost_center = dunning_type.cost_center letter_text = get_dunning_letter_text( - dunning_type=dunning_type.name, - doc=target.as_dict(), - language=source.language + dunning_type=dunning_type.name, doc=target.as_dict(), language=source.language ) if letter_text: @@ -2542,26 +2542,19 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): table_maps={ "Sales Invoice": { "doctype": "Dunning", - "field_map": { - "customer_address": "customer_address", - "parent": "sales_invoice" - }, + "field_map": {"customer_address": "customer_address", "parent": "sales_invoice"}, }, "Payment Schedule": { "doctype": "Overdue Payment", - "field_map": { - "name": "payment_schedule", - "parent": "sales_invoice" - }, + "field_map": {"name": "payment_schedule", "parent": "sales_invoice"}, "condition": lambda doc: doc.outstanding > 0 and getdate(doc.due_date) < getdate(), - } + }, }, postprocess=postprocess_dunning, - ignore_permissions=ignore_permissions + ignore_permissions=ignore_permissions, ) - def check_if_return_invoice_linked_with_payment_entry(self): # If a Return invoice is linked with payment entry along with other invoices, # the cancellation of the Return causes allocated amount to be greater than paid diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py index 90966aa4cb..7a8e591798 100644 --- a/erpnext/patches/v14_0/single_to_multi_dunning.py +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -18,7 +18,8 @@ def execute(): # something's already here, doesn't need patching continue - payment_schedules = frappe.get_all("Payment Schedule", + payment_schedules = frappe.get_all( + "Payment Schedule", filters={"parent": dunning.sales_invoice}, fields=[ "parent as sales_invoice", @@ -30,8 +31,8 @@ def execute(): # at the time of creating this dunning, the full amount was outstanding "payment_amount as outstanding", "'0' as paid_amount", - "discounted_amount" - ] + "discounted_amount", + ], ) dunning.extend("overdue_payments", payment_schedules) From 4673aa412e0e2aec1bc82df4b7264f6fd6f3c680 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 15 Jun 2023 15:47:18 +0530 Subject: [PATCH 054/107] fix: Broken pop-up and references to non-existent field - `child_fieldname` misspelled causing broken pop up to fetch overdue payments - `sales_invoice` referenced in dunning fields, which has been removed - Fetch `customer_name` from `customer` link field --- erpnext/accounts/doctype/dunning/dunning.js | 2 +- erpnext/accounts/doctype/dunning/dunning.json | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index a99b44ff1e..8171bb93ef 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -72,7 +72,7 @@ frappe.ui.form.on("Dunning", { company: frm.doc.company }, allow_child_item_selection: true, - child_fielname: "payment_schedule", + child_fieldname: "payment_schedule", child_columns: ["due_date", "outstanding"], }); }); diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index 20e843c922..b7e8aeaaaf 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -75,7 +75,7 @@ "print_hide": 1 }, { - "fetch_from": "sales_invoice.customer_name", + "fetch_from": "customer.customer_name", "fieldname": "customer_name", "fieldtype": "Data", "in_list_view": 1, @@ -184,21 +184,18 @@ "label": "Address and Contact" }, { - "fetch_from": "sales_invoice.address_display", "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", "read_only": 1 }, { - "fetch_from": "sales_invoice.contact_display", "fieldname": "contact_display", "fieldtype": "Small Text", "label": "Contact", "read_only": 1 }, { - "fetch_from": "sales_invoice.contact_mobile", "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", @@ -206,14 +203,12 @@ "read_only": 1 }, { - "fetch_from": "sales_invoice.company_address_display", "fieldname": "company_address_display", "fieldtype": "Small Text", "label": "Company Address Display", "read_only": 1 }, { - "fetch_from": "sales_invoice.contact_email", "fieldname": "contact_email", "fieldtype": "Data", "label": "Contact Email", @@ -221,7 +216,6 @@ "read_only": 1 }, { - "fetch_from": "sales_invoice.customer", "fieldname": "customer", "fieldtype": "Link", "label": "Customer", @@ -387,7 +381,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:24:01.677026", + "modified": "2023-06-15 15:46:53.865712", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning", From 254bab33da379d223751149414921145a631981e Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 15 Jun 2023 19:00:24 +0530 Subject: [PATCH 055/107] fix: Consider installments/partial payments while back updating Dunning - Also use data from Overdue Payment table and not just Dunning parent document --- erpnext/accounts/doctype/dunning/dunning.py | 33 ++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index e0d75d3b47..1daaf0682a 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -75,16 +75,12 @@ def resolve_dunning(doc, state): when a Payment Entry is submitted. """ for reference in doc.references: - if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0: - unresolved_dunnings = frappe.get_all( - "Dunning", - filters={ - "sales_invoice": reference.reference_name, - "status": ("!=", "Resolved"), - "docstatus": ("!=", 2), - }, - pluck="name", - ) + # Consider partial and full payments + if ( + reference.reference_doctype == "Sales Invoice" + and reference.outstanding_amount < reference.total_amount + ): + unresolved_dunnings = get_unresolved_dunnings(reference.reference_name) for dunning_name in unresolved_dunnings: resolve = True @@ -104,6 +100,23 @@ def resolve_dunning(doc, state): dunning.save() +def get_unresolved_dunnings(sales_invoice): + dunning = frappe.qb.DocType("Dunning") + overdue_payment = frappe.qb.DocType("Overdue Payment") + + return ( + frappe.qb.from_(dunning) + .join(overdue_payment) + .on(overdue_payment.parent == dunning.name) + .select(dunning.name) + .where( + (dunning.status != "Resolved") + & (dunning.docstatus != 2) + & (overdue_payment.sales_invoice == sales_invoice) + ) + ).run(as_dict=True) + + @frappe.whitelist() def get_dunning_letter_text(dunning_type, doc, language=None): if isinstance(doc, str): From c32113918ea92038aae94461fd61e6bcc8ade626 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 15 Jun 2023 20:04:54 +0530 Subject: [PATCH 056/107] fix: Updation of dunning on PE cancellation --- erpnext/accounts/doctype/dunning/dunning.py | 35 ++++++++++++--------- erpnext/hooks.py | 1 + 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 1daaf0682a..1447ac03f0 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -75,16 +75,23 @@ def resolve_dunning(doc, state): when a Payment Entry is submitted. """ for reference in doc.references: - # Consider partial and full payments - if ( - reference.reference_doctype == "Sales Invoice" - and reference.outstanding_amount < reference.total_amount - ): - unresolved_dunnings = get_unresolved_dunnings(reference.reference_name) + # Consider partial and full payments: + # Submitting full payment: outstanding_amount will be 0 + # Submitting 1st partial payment: outstanding_amount will be the pending installment + # Cancelling full payment: outstanding_amount will revert to total amount + # Cancelling last partial payment: outstanding_amount will revert to pending amount + submit_condition = reference.outstanding_amount < reference.total_amount + cancel_condition = reference.outstanding_amount <= reference.total_amount - for dunning_name in unresolved_dunnings: + if reference.reference_doctype == "Sales Invoice" and ( + submit_condition if doc.docstatus == 1 else cancel_condition + ): + state = "Resolved" if doc.docstatus == 2 else "Unresolved" + dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state) + + for dunning in dunnings: resolve = True - dunning = frappe.get_doc("Dunning", dunning_name) + dunning = frappe.get_doc("Dunning", dunning.get("name")) for overdue_payment in dunning.overdue_payments: outstanding_inv = frappe.get_value( "Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount" @@ -92,15 +99,13 @@ def resolve_dunning(doc, state): outstanding_ps = frappe.get_value( "Payment Schedule", overdue_payment.payment_schedule, "outstanding" ) - if outstanding_ps > 0 and outstanding_inv > 0: - resolve = False + resolve = False if (outstanding_ps > 0 and outstanding_inv > 0) else True - if resolve: - dunning.status = "Resolved" - dunning.save() + dunning.status = "Resolved" if resolve else "Unresolved" + dunning.save() -def get_unresolved_dunnings(sales_invoice): +def get_linked_dunnings_as_per_state(sales_invoice, state): dunning = frappe.qb.DocType("Dunning") overdue_payment = frappe.qb.DocType("Overdue Payment") @@ -110,7 +115,7 @@ def get_unresolved_dunnings(sales_invoice): .on(overdue_payment.parent == dunning.name) .select(dunning.name) .where( - (dunning.status != "Resolved") + (dunning.status == state) & (dunning.docstatus != 2) & (overdue_payment.sales_invoice == sales_invoice) ) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c821fcf4e6..6d64f64d1d 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -334,6 +334,7 @@ doc_events = { "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning", ], + "on_cancel": ["erpnext.accounts.doctype.dunning.dunning.resolve_dunning"], "on_trash": "erpnext.regional.check_deletion_permission", }, "Address": { From 47852803f0bbe578ffcb4160170eaf0120a1eb4c Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 16 Jun 2023 14:10:07 +0530 Subject: [PATCH 057/107] fix: Set Address via JS and Py files (for API usecases) --- erpnext/accounts/doctype/dunning/dunning.js | 3 +++ erpnext/accounts/doctype/dunning/dunning.py | 29 +++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 8171bb93ef..7c4e9529a7 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -140,6 +140,9 @@ frappe.ui.form.on("Dunning", { frm.trigger("conversion_rate"); } }, + customer: (frm) => { + erpnext.utils.get_party_details(frm); + }, conversion_rate: function (frm) { if (frm.doc.currency === erpnext.get_currency(frm.doc.company)) { frm.set_value("conversion_rate", 1.0); diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 1447ac03f0..c8cfbca27d 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -11,12 +11,11 @@ -> Resolves dunning automatically """ -from __future__ import unicode_literals - import json import frappe from frappe import _ +from frappe.contacts.doctype.address.address import get_address_display from frappe.utils import getdate from erpnext.controllers.accounts_controller import AccountsController @@ -27,6 +26,7 @@ class Dunning(AccountsController): self.validate_same_currency() self.validate_overdue_payments() self.validate_totals() + self.set_party_details() self.set_dunning_level() def validate_same_currency(self): @@ -56,6 +56,31 @@ class Dunning(AccountsController): self.base_dunning_amount = self.dunning_amount * self.conversion_rate self.grand_total = self.total_outstanding + self.dunning_amount + def set_party_details(self): + from erpnext.accounts.party import _get_party_details + + party_details = _get_party_details( + self.customer, + ignore_permissions=self.flags.ignore_permissions, + doctype=self.doctype, + company=self.company, + posting_date=self.get("posting_date"), + fetch_payment_terms_template=False, + party_address=self.customer_address, + company_address=self.get("company_address"), + ) + for field in [ + "customer_address", + "address_display", + "company_address", + "contact_person", + "contact_display", + "contact_mobile", + ]: + self.set(field, party_details.get(field)) + + self.set("company_address_display", get_address_display(self.company_address)) + def set_dunning_level(self): for row in self.overdue_payments: past_dunnings = frappe.get_all( From 8f2e5288ff8d1651b8d41a7c7b977e99b65506c4 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 20 Jun 2023 11:47:04 +0530 Subject: [PATCH 058/107] test: Dunning and PE against partially due invoice - Check if the right payment portion is picked - Check if the SI and Dunning are updated on submission and cancellation of PE --- erpnext/accounts/doctype/dunning/dunning.js | 2 +- erpnext/accounts/doctype/dunning/dunning.py | 2 +- .../accounts/doctype/dunning/test_dunning.py | 86 +++++++++++++++++-- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index 7c4e9529a7..1ac909e745 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -1,4 +1,4 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt frappe.ui.form.on("Dunning", { diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index c8cfbca27d..9d0d36b970 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt """ # Accounting diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index be8c533d8d..b29ace275f 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -1,9 +1,7 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, nowdate, today from erpnext import get_default_cost_center @@ -21,9 +19,10 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( test_dependencies = ["Company", "Cost Center"] -class TestDunning(unittest.TestCase): +class TestDunning(FrappeTestCase): @classmethod def setUpClass(cls): + super().setUpClass() create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1) create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0) unlink_payment_on_cancel_of_invoice() @@ -31,8 +30,9 @@ class TestDunning(unittest.TestCase): @classmethod def tearDownClass(cls): unlink_payment_on_cancel_of_invoice(0) + super().tearDownClass() - def test_first_dunning(self): + def test_dunning_without_fees(self): dunning = create_dunning(overdue_days=20) self.assertEqual(round(dunning.total_outstanding, 2), 100.00) @@ -41,7 +41,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.dunning_amount, 2), 0.00) self.assertEqual(round(dunning.grand_total, 2), 100.00) - def test_second_dunning(self): + def test_dunning_with_fees_and_interest(self): dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC") self.assertEqual(round(dunning.total_outstanding, 2), 100.00) @@ -50,7 +50,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(dunning.dunning_amount, 2), 10.41) self.assertEqual(round(dunning.grand_total, 2), 110.41) - def test_payment_entry(self): + def test_dunning_with_payment_entry(self): dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC") dunning.submit() pe = get_payment_entry("Dunning", dunning.name) @@ -68,6 +68,44 @@ class TestDunning(unittest.TestCase): dunning.reload() self.assertEqual(dunning.status, "Resolved") + def test_dunning_and_payment_against_partially_due_invoice(self): + """ + Create SI with first installment overdue. Check impact of Dunning and Payment Entry. + """ + create_payment_terms_template_for_dunning() + sales_invoice = create_sales_invoice_against_cost_center( + posting_date=add_days(today(), -1 * 6), + qty=1, + rate=100, + do_not_submit=True, + ) + sales_invoice.payment_terms_template = "_Test 50-50 for Dunning" + sales_invoice.submit() + dunning = create_dunning_from_sales_invoice(sales_invoice.name) + + self.assertEqual(len(dunning.overdue_payments), 1) + self.assertEqual(dunning.overdue_payments[0].payment_term, "_Test Payment Term 1 for Dunning") + + dunning.submit() + pe = get_payment_entry("Dunning", dunning.name) + pe.reference_no, pe.reference_date = "2", nowdate() + pe.insert() + pe.submit() + sales_invoice.load_from_db() + dunning.load_from_db() + + self.assertEqual(sales_invoice.status, "Partly Paid") + self.assertEqual(sales_invoice.payment_schedule[0].outstanding, 0) + self.assertEqual(dunning.status, "Resolved") + + # Test impact on cancellation of PE + pe.cancel() + sales_invoice.reload() + dunning.reload() + + self.assertEqual(sales_invoice.status, "Overdue") + self.assertEqual(dunning.status, "Unresolved") + def create_dunning(overdue_days, dunning_type_name=None): posting_date = add_days(today(), -1 * overdue_days) @@ -125,3 +163,35 @@ def get_income_account(company): pluck="name", )[0] ) + + +def create_payment_terms_template_for_dunning(): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term + + create_payment_term("_Test Payment Term 1 for Dunning") + create_payment_term("_Test Payment Term 2 for Dunning") + + if not frappe.db.exists("Payment Terms Template", "_Test 50-50 for Dunning"): + frappe.get_doc( + { + "doctype": "Payment Terms Template", + "template_name": "_Test 50-50 for Dunning", + "allocate_payment_based_on_payment_terms": 1, + "terms": [ + { + "doctype": "Payment Terms Template Detail", + "payment_term": "_Test Payment Term 1 for Dunning", + "invoice_portion": 50.00, + "credit_days_based_on": "Day(s) after invoice date", + "credit_days": 5, + }, + { + "doctype": "Payment Terms Template Detail", + "payment_term": "_Test Payment Term 2 for Dunning", + "invoice_portion": 50.00, + "credit_days_based_on": "Day(s) after invoice date", + "credit_days": 10, + }, + ], + } + ).insert() From 5a952987a316185f50d80e7a646d8edb84512105 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 28 Jun 2023 17:13:34 +0530 Subject: [PATCH 059/107] fix: Use `this.frm` (Linter) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 053b1a324c..d21a50c1c3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -149,8 +149,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e ); if (payment_is_overdue) { - cur_frm.add_custom_button(__('Dunning'), function () { - cur_frm.events.create_dunning(cur_frm); + this.frm.add_custom_button(__('Dunning'), () => { + this.frm.events.create_dunning(this.frm); }, __('Create')); } } From da72bd98196623fd2246a1a9129991efbb65ab13 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 30 Jun 2023 15:11:45 +0200 Subject: [PATCH 060/107] feat: add Lead to Adresse And contact report --- .../selling/report/address_and_contacts/address_and_contacts.js | 2 +- .../selling/report/address_and_contacts/address_and_contacts.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.js b/erpnext/selling/report/address_and_contacts/address_and_contacts.js index ef87586f66..8aa14d1998 100644 --- a/erpnext/selling/report/address_and_contacts/address_and_contacts.js +++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.js @@ -13,7 +13,7 @@ frappe.query_reports["Address And Contacts"] = { "get_query": function() { return { "filters": { - "name": ["in","Customer,Supplier,Sales Partner"], + "name": ["in","Customer,Supplier,Sales Partner,Lead"], } } } diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.py b/erpnext/selling/report/address_and_contacts/address_and_contacts.py index 9a1cfda847..4542bdff43 100644 --- a/erpnext/selling/report/address_and_contacts/address_and_contacts.py +++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.py @@ -130,6 +130,7 @@ def get_party_group(party_type): "Customer": "customer_group", "Supplier": "supplier_group", "Sales Partner": "partner_type", + "Lead": "status", } return group[party_type] From a939431d48efc05896a356e8fd4993e59af6c6cb Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 3 Jul 2023 21:03:24 +0200 Subject: [PATCH 061/107] fix: german translations --- erpnext/translations/de.csv | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 26a775e9de..c02aeb2f83 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -3079,9 +3079,9 @@ Total Leaves,insgesamt Blätter, Total Order Considered,Geschätzte Summe der Bestellungen, Total Order Value,Gesamtbestellwert, Total Outgoing,Summe Auslieferungen, -Total Outstanding,Absolut aussergewöhnlich, -Total Outstanding Amount,Offener Gesamtbetrag, -Total Outstanding: {0},Gesamtsumme: {0}, +Total Outstanding,Summe ausstehende Beträge, +Total Outstanding Amount,Summe ausstehende Beträge, +Total Outstanding: {0},Summe ausstehende Beträge: {0}, Total Paid Amount,Summe gezahlte Beträge, Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total,Der gesamte Zahlungsbetrag im Zahlungsplan muss gleich Groß / Abgerundet sein, Total Payments,Gesamtzahlungen, @@ -8537,13 +8537,14 @@ If this is unchecked Journal Entries will be saved in a Draft state and will hav Enable Distributed Cost Center,Aktivieren Sie die verteilte Kostenstelle, Distributed Cost Center,Verteilte Kostenstelle, Dunning,Mahnung, +Dunning Level,Mahnstufe, DUNN-.MM.-.YY.-,DUNN-.MM .-. YY.-, Overdue Days,Überfällige Tage, Dunning Type,Mahnart, Dunning Fee,Mahngebühr, Dunning Amount,Mahnbetrag, -Resolved,Aufgelöst, -Unresolved,Ungelöst, +Resolved,Geklärt, +Unresolved,Ungeklärt, Printing Setting,Druckeinstellung, Body Text,Hauptteil, Closing Text,Text schließen, @@ -8723,7 +8724,7 @@ Company {0} already exists. Continuing will overwrite the Company and Chart of A Meta Data,Metadaten, Unresolve,Auflösen, Create Document,Dokument erstellen, -Mark as unresolved,Als ungelöst markieren, +Mark as unresolved,Als ungeklärt markieren, TaxJar Settings,TaxJar-Einstellungen, Sandbox Mode,Sandbox-Modus, Enable Tax Calculation,Steuerberechnung aktivieren, From dd8c3d5462c97f34d03f76f6e797391ad401b56d Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 12 Jul 2023 10:00:18 +0530 Subject: [PATCH 062/107] feat: filter based on accounting dimension in profitability analysis --- .../profitability_analysis.js | 15 ++++++++++++++- .../profitability_analysis.py | 10 +++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 889ede5a82..4412212d2e 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -16,10 +16,23 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldname": "based_on", "label": __("Based On"), "fieldtype": "Select", - "options": ["Cost Center", "Project"], + "options": ["Cost Center", "Project", "Accounting Dimension"], "default": "Cost Center", "reqd": 1 }, + { + "fieldname": "accounting_dimension", + "label": __("Accounting Dimension"), + "fieldtype": "Link", + "options": "Accounting Dimension", + "get_query": () =>{ + return { + filters: { + "disabled": 0 + } + } + } + }, { "fieldname": "fiscal_year", "label": __("Fiscal Year"), diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index 183e279fe5..c05aa94457 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -16,8 +16,8 @@ value_fields = ("income", "expense", "gross_profit_loss") def execute(filters=None): - if not filters.get("based_on"): - filters["based_on"] = "Cost Center" + if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"): + frappe.throw(_("Select Accounting Dimension.")) based_on = filters.based_on.replace(" ", "_").lower() validate_filters(filters) @@ -37,6 +37,8 @@ def get_accounts_data(based_on, company): ) elif based_on == "project": return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name") + elif based_on == "accounting_dimension": + return frappe.get_all("Accounting Dimension", fields=["name"], order_by="name") else: filters = {} doctype = frappe.unscrub(based_on) @@ -60,7 +62,9 @@ def get_data(accounts, filters, based_on): filters.get("company"), filters.get("from_date"), filters.get("to_date"), - based_on, + based_on + if based_on != "accounting_dimension" + else filters.accounting_dimension.replace(" ", "_").lower(), gl_entries_by_account, ignore_closing_entries=not flt(filters.get("with_period_closing_entry")), ) From 21c993a7b3129696ac4efa3d2dd2ac3f4d3a5ec2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 12 Jul 2023 20:32:08 +0530 Subject: [PATCH 063/107] fix: clear accounting dimension value when based on field changes --- .../profitability_analysis/profitability_analysis.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 4412212d2e..b45fe6fe5d 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -18,7 +18,15 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Select", "options": ["Cost Center", "Project", "Accounting Dimension"], "default": "Cost Center", - "reqd": 1 + "reqd": 1, + "on_change": function(query_report){ + let based_on = query_report.get_values().based_on; + if(based_on!='Accounting Dimension'){ + frappe.query_report.set_filter_value({ + accounting_dimension: '' + }); + } + } }, { "fieldname": "accounting_dimension", From 924cdef6d9631e2d84a775b94e211c71d7e5b57a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 Jul 2023 18:53:02 +0530 Subject: [PATCH 064/107] ci: add eslint and update linting confs --- .eslintrc | 65 +++++-------------- .github/workflows/linters.yml | 9 +-- .pre-commit-config.yaml | 18 ++++- .../report/account_balance/account_balance.js | 2 +- .../billed_items_to_be_received.js | 4 +- .../consolidated_financial_statement.js | 2 +- .../customer_ledger_summary.js | 2 +- .../deferred_revenue_and_expense.js | 2 +- .../dimension_wise_accounts_balance_report.js | 2 +- .../gross_and_net_profit_report.js | 2 +- .../inactive_sales_items.js | 2 +- .../report/payment_ledger/payment_ledger.js | 2 +- .../report/pos_register/pos_register.js | 2 +- .../report/share_balance/share_balance.js | 2 +- .../report/share_ledger/share_ledger.js | 2 +- .../supplier_ledger_summary.js | 2 +- .../tds_computation_summary.js | 2 +- .../tds_payable_monthly.js | 2 +- .../voucher_wise_balance.js | 2 +- .../fixed_asset_register.js | 2 +- .../procurement_tracker.js | 2 +- .../purchase_analytics/purchase_analytics.js | 9 +-- .../purchase_order_analysis.js | 2 +- .../requested_items_to_order_and_receive.js | 2 +- .../subcontract_order_summary.js | 2 +- .../subcontracted_item_to_be_received.js | 2 +- ...tracted_raw_materials_to_be_transferred.js | 2 +- .../first_response_time_for_opportunity.js | 2 +- .../lead_conversion_time.js | 2 +- .../crm/report/lead_details/lead_details.js | 2 +- .../lost_opportunity/lost_opportunity.js | 2 +- .../opportunity_summary_by_sales_stage.js | 2 +- .../sales_pipeline_analytics.js | 2 +- .../doctype/workstation/_test_workstation.js | 2 +- .../doctype/workstation/workstation_list.js | 2 +- .../report/bom_explorer/bom_explorer.js | 2 +- .../bom_operations_time.js | 2 +- .../bom_stock_calculated.js | 2 +- .../bom_variance_report.js | 2 +- .../cost_of_poor_quality_report.js | 2 +- .../downtime_analysis/downtime_analysis.js | 2 +- .../exponential_smoothing_forecasting.js | 2 +- .../job_card_summary/job_card_summary.js | 2 +- .../process_loss_report.js | 2 +- .../production_analytics.js | 2 +- .../production_plan_summary.js | 2 +- .../production_planning_report.js | 2 +- .../quality_inspection_summary.js | 2 +- .../work_order_consumed_materials.js | 2 +- .../work_order_stock_report.js | 2 +- .../work_order_summary/work_order_summary.js | 2 +- .../delayed_tasks_summary.js | 2 +- .../employee_billing_summary.js | 2 +- .../project_billing_summary.js | 2 +- .../report/project_summary/project_summary.js | 2 +- erpnext/public/js/controllers/buying.js | 2 +- .../electronic_invoice_register.js | 2 +- .../report/uae_vat_201/uae_vat_201.js | 2 +- .../vat_audit_report/vat_audit_report.js | 2 +- .../address_and_contacts.js | 2 +- .../customer_wise_item_price.js | 2 +- .../item_wise_sales_history.js | 2 +- .../payment_terms_status_for_sales_order.js | 6 +- .../pending_so_items_for_purchase_request.js | 2 +- .../report/sales_analytics/sales_analytics.js | 2 +- .../sales_order_analysis.js | 2 +- .../sales_partner_commission_summary.js | 2 +- ...ner_target_variance_based_on_item_group.js | 2 +- .../sales_partner_transaction_summary.js | 2 +- .../sales_person_commission_summary.js | 2 +- ...son_target_variance_based_on_item_group.js | 2 +- ...ory_target_variance_based_on_item_group.js | 2 +- .../territory_wise_sales.js | 2 +- .../cogs_by_item_group/cogs_by_item_group.js | 2 +- .../delayed_item_report.js | 2 +- .../delayed_order_report.js | 2 +- ...eue_vs_qty_after_transaction_comparison.js | 2 +- ...incorrect_balance_qty_after_transaction.js | 2 +- .../incorrect_serial_no_valuation.js | 2 +- .../incorrect_stock_value_report.js | 2 +- .../item_price_stock/item_price_stock.js | 2 +- .../item_shortage_report.js | 2 +- .../item_variant_details.js | 2 +- .../serial_no_ledger/serial_no_ledger.js | 2 +- .../report/stock_analytics/stock_analytics.js | 8 +-- .../stock_and_account_value_comparison.js | 2 +- .../stock_ledger_invariant_check.js | 2 +- .../stock_qty_vs_serial_no_count.js | 2 +- .../total_stock_summary.js | 2 +- ...rehouse_wise_item_balance_age_and_value.js | 2 +- .../warehouse_wise_stock_balance.js | 2 +- .../first_response_time_for_issues.js | 2 +- .../report/issue_analytics/issue_analytics.js | 8 +-- .../report/issue_summary/issue_summary.js | 2 +- .../support_hour_distribution.js | 2 +- .../youtube_interactions.js | 2 +- 96 files changed, 145 insertions(+), 158 deletions(-) diff --git a/.eslintrc b/.eslintrc index 12fefa0968..f3d4fd5091 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,65 +2,32 @@ "env": { "browser": true, "node": true, - "es6": true + "es2022": true }, "parserOptions": { - "ecmaVersion": 11, "sourceType": "module" }, "extends": "eslint:recommended", "rules": { - "indent": [ - "error", - "tab", - { "SwitchCase": 1 } - ], - "brace-style": [ - "error", - "1tbs" - ], - "space-unary-ops": [ - "error", - { "words": true } - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "off" - ], - "semi": [ - "warn", - "always" - ], - "camelcase": [ - "off" - ], - "no-unused-vars": [ - "warn" - ], - "no-redeclare": [ - "warn" - ], - "no-console": [ - "warn" - ], - "no-extra-boolean-cast": [ - "off" - ], - "no-control-regex": [ - "off" - ], - "space-before-blocks": "warn", - "keyword-spacing": "warn", - "comma-spacing": "warn", - "key-spacing": "warn" + "indent": "off", + "brace-style": "off", + "no-mixed-spaces-and-tabs": "off", + "no-useless-escape": "off", + "space-unary-ops": ["error", { "words": true }], + "linebreak-style": "off", + "quotes": ["off"], + "semi": "off", + "camelcase": "off", + "no-unused-vars": "off", + "no-console": ["warn"], + "no-extra-boolean-cast": ["off"], + "no-control-regex": ["off"] }, "root": true, "globals": { "frappe": true, "Vue": true, + "SetVueGlobals": true, "erpnext": true, "hub": true, "$": true, @@ -97,8 +64,10 @@ "is_null": true, "in_list": true, "has_common": true, + "posthog": true, "has_words": true, "validate_email": true, + "open_web_template_values_editor": true, "get_number_format": true, "format_number": true, "format_currency": true, diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index af6d8f26a7..94b76b12ce 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -9,21 +9,22 @@ jobs: name: linters runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.10' + cache: pip - name: Install and Run Pre-commit - uses: pre-commit/action@v2.0.3 + uses: pre-commit/action@v3.0.0 - name: Download Semgrep rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules - name: Download semgrep - run: pip install semgrep==0.97.0 + run: pip install semgrep - name: Run Semgrep rules run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d70977c07e..93d07485e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,8 +16,24 @@ repos: - id: check-merge-conflict - id: check-ast + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.44.0 + hooks: + - id: eslint + types_or: [javascript] + args: ['--quiet'] + # Ignore any files that might contain jinja / bundles + exclude: | + (?x)^( + erpnext/public/dist/.*| + cypress/.*| + .*node_modules.*| + .*boilerplate.*| + erpnext/public/js/controllers/.* + )$ + - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 additional_dependencies: [ diff --git a/erpnext/accounts/report/account_balance/account_balance.js b/erpnext/accounts/report/account_balance/account_balance.js index bb66951cdc..5681be9211 100644 --- a/erpnext/accounts/report/account_balance/account_balance.js +++ b/erpnext/accounts/report/account_balance/account_balance.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Account Balance"] = { "filters": [ diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js index e1fccb6e72..7617ed1e22 100644 --- a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports['Billed Items To Be Received'] = { 'filters': [ @@ -17,7 +17,7 @@ frappe.query_reports['Billed Items To Be Received'] = { 'fieldname': 'posting_date', 'fieldtype': 'Date', 'reqd': 1, - 'default': get_today() + 'default': frappe.datetime.get_today() }, { 'label': __('Purchase Invoice'), diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index dd965a9813..c6600b9aa7 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.require("assets/erpnext/js/financial_statements.js", function() { frappe.query_reports["Consolidated Financial Statement"] = { diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js index a123631663..74d52de2d2 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Customer Ledger Summary"] = { "filters": [ diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js index 0056b9e8f5..eb2df6284b 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + function get_filters() { let filters = [ diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js index ea05a35b25..bd4b274f4d 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.require("assets/erpnext/js/financial_statements.js", function() { frappe.query_reports["Dimension-wise Accounts Balance Report"] = { diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js index 92cf36ebc5..f6b0b8c3f7 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Gross and Net Profit Report"] = { "filters": [ diff --git a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js index 7908c07a0a..bd9b54398b 100644 --- a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js +++ b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Inactive Sales Items"] = { "filters": [ diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js index a5a4108f1d..65380ccdda 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.js +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js @@ -1,6 +1,6 @@ // Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + function get_filters() { let filters = [ diff --git a/erpnext/accounts/report/pos_register/pos_register.js b/erpnext/accounts/report/pos_register/pos_register.js index b8d48d92de..6e5491a0f8 100644 --- a/erpnext/accounts/report/pos_register/pos_register.js +++ b/erpnext/accounts/report/pos_register/pos_register.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["POS Register"] = { "filters": [ diff --git a/erpnext/accounts/report/share_balance/share_balance.js b/erpnext/accounts/report/share_balance/share_balance.js index 6db5bdd299..ac64a0bfb9 100644 --- a/erpnext/accounts/report/share_balance/share_balance.js +++ b/erpnext/accounts/report/share_balance/share_balance.js @@ -1,7 +1,7 @@ // -*- coding: utf-8 -*- // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Share Balance"] = { "filters": [ diff --git a/erpnext/accounts/report/share_ledger/share_ledger.js b/erpnext/accounts/report/share_ledger/share_ledger.js index 6d1c44a6d0..4f2d2cc78f 100644 --- a/erpnext/accounts/report/share_ledger/share_ledger.js +++ b/erpnext/accounts/report/share_ledger/share_ledger.js @@ -1,7 +1,7 @@ // -*- coding: utf-8 -*- // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Share Ledger"] = { "filters": [ diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index 5dc4c3d1c1..8e3c8ac630 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Supplier Ledger Summary"] = { "filters": [ diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js index d3d45b353a..d3348460a8 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["TDS Computation Summary"] = { "filters": [ diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js index ff2aa30601..202fd1d15a 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["TDS Payable Monthly"] = { "filters": [ diff --git a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js index 0c148f85fb..f7ab029f19 100644 --- a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js +++ b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js @@ -1,6 +1,6 @@ // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Voucher-wise Balance"] = { "filters": [ diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index b788a32d6a..2752e09909 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Fixed Asset Register"] = { "filters": [ diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.js b/erpnext/buying/report/procurement_tracker/procurement_tracker.js index 283d56c946..416655d6bc 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.js +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Procurement Tracker"] = { "filters": [ diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index a884f06d2c..a0faa6869a 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Purchase Analytics"] = { "filters": [ @@ -81,8 +81,9 @@ frappe.query_reports["Purchase Analytics"] = { const tree_type = frappe.query_report.filters[0].value; if (data_doctype != tree_type) return; - row_name = data[2].content; - length = data.length; + let row_name = data[2].content; + let length = data.length; + let row_values = ''; if (tree_type == "Supplier") { row_values = data @@ -104,7 +105,7 @@ frappe.query_reports["Purchase Analytics"] = { }); } - entry = { + let entry = { name: row_name, values: row_values, }; diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 721e54e46f..91506c0ab3 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Purchase Order Analysis"] = { "filters": [ diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js index d727584d0a..cb05109d5b 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Requested Items to Order and Receive"] = { "filters": [ diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js index 075671f4ec..800b8ab7db 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Subcontract Order Summary"] = { "filters": [ diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js index 9db769d59b..35be2a9cf8 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Subcontracted Item To Be Received"] = { "filters": [ diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js index 7e5338f353..33b26dcb5f 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = { "filters": [ diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js index fe5707af29..4bf82479a1 100644 --- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js +++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["First Response Time for Opportunity"] = { "filters": [ diff --git a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js index eeb8984513..d7ff9ad538 100644 --- a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js +++ b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js @@ -1,6 +1,6 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Lead Conversion Time"] = { "filters": [ diff --git a/erpnext/crm/report/lead_details/lead_details.js b/erpnext/crm/report/lead_details/lead_details.js index 2f6d24224f..66611f6c6c 100644 --- a/erpnext/crm/report/lead_details/lead_details.js +++ b/erpnext/crm/report/lead_details/lead_details.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Lead Details"] = { "filters": [ diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.js b/erpnext/crm/report/lost_opportunity/lost_opportunity.js index 927c54df07..8d5923950a 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.js +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Lost Opportunity"] = { "filters": [ diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js index 7cd1710a7f..0aa21436bc 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Opportunity Summary by Sales Stage"] = { "filters": [ diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js index 1426f4b6fd..3111121522 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Pipeline Analytics"] = { "filters": [ diff --git a/erpnext/manufacturing/doctype/workstation/_test_workstation.js b/erpnext/manufacturing/doctype/workstation/_test_workstation.js index 0f09bd1c61..f2dced81e2 100644 --- a/erpnext/manufacturing/doctype/workstation/_test_workstation.js +++ b/erpnext/manufacturing/doctype/workstation/_test_workstation.js @@ -1,4 +1,4 @@ -/* eslint-disable */ + // rename this file from _test_[name] to test_[name] to activate // and remove above this line diff --git a/erpnext/manufacturing/doctype/workstation/workstation_list.js b/erpnext/manufacturing/doctype/workstation/workstation_list.js index 6a89d21e1e..61f2062ec0 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_list.js +++ b/erpnext/manufacturing/doctype/workstation/workstation_list.js @@ -1,4 +1,4 @@ -/* eslint-disable */ + frappe.listview_settings['Workstation'] = { // add_fields: ["status"], // filters:[["status","=", "Open"]] diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.js b/erpnext/manufacturing/report/bom_explorer/bom_explorer.js index b94d3f3770..50191bf8ab 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.js +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["BOM Explorer"] = { "filters": [ diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 0eb22a22f7..34edb9d538 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["BOM Operations Time"] = { "filters": [ diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js index a0fd91e866..8e66f704c8 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Epoch Consulting and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["BOM Stock Calculated"] = { "filters": [ diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.js b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.js index c6ecaef2fa..db6f4d7688 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.js +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["BOM Variance Report"] = { "filters": [ diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js index 72eed5e0d7..d0249ba84b 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Cost of Poor Quality Report"] = { "filters": [ diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js index f6486743aa..0589260958 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Downtime Analysis"] = { "filters": [ diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js index a3f0d00877..3795bd3df8 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Exponential Smoothing Forecasting"] = { "filters": [ diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js index 782ce8110a..aa4251cd50 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Job Card Summary"] = { "filters": [ diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.js b/erpnext/manufacturing/report/process_loss_report/process_loss_report.js index b0c2b94a25..c08413dc4a 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.js +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Process Loss Report"] = { filters: [ diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.js b/erpnext/manufacturing/report/production_analytics/production_analytics.js index 99f9b1260a..72f06edf85 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.js +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Production Analytics"] = { "filters": [ diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js index 59396fef16..521543ab1b 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Production Plan Summary"] = { "filters": [ diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js index 675b8a1100..422583274b 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Production Planning Report"] = { "filters": [ diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js index d4587aa661..905d185076 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Quality Inspection Summary"] = { "filters": [ diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js index 2fb4ec6791..70d7f92da0 100644 --- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Work Order Consumed Materials"] = { "filters": [ diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.js b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.js index dbb7c23410..cf651cb394 100644 --- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.js +++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Work Order Stock Report"] = { "filters": [ diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index 67bd24dd80..d0242f4d3b 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Work Order Summary"] = { "filters": [ diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js index 5aa44c0a8c..fa70b9394a 100644 --- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Delayed Tasks Summary"] = { "filters": [ diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js index 13f49ed6be..8566b1fc78 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Employee Billing Summary"] = { "filters": [ diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js index caac1d86b4..0242036dc7 100644 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Project Billing Summary"] = { "filters": [ diff --git a/erpnext/projects/report/project_summary/project_summary.js b/erpnext/projects/report/project_summary/project_summary.js index 414b7b206a..21dbfda73f 100644 --- a/erpnext/projects/report/project_summary/project_summary.js +++ b/erpnext/projects/report/project_summary/project_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Project Summary"] = { "filters": [ diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index c001b4eb56..75d9891b81 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -34,7 +34,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac this.frm.set_value("disable_rounded_total", disable); } - /* eslint-disable */ + // no idea where me is coming from if(this.frm.get_field('shipping_address')) { this.frm.set_query("shipping_address", function() { diff --git a/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js b/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js index d7e3ac9a5d..4fc1be1ff5 100644 --- a/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js +++ b/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Electronic Invoice Register"] = { "filters": [ diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.js b/erpnext/regional/report/uae_vat_201/uae_vat_201.js index 5957424770..eaefc0420a 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.js +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["UAE VAT 201"] = { "filters": [ diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.js b/erpnext/regional/report/vat_audit_report/vat_audit_report.js index 39ef9b563a..41318f3160 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.js +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["VAT Audit Report"] = { "filters": [ diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.js b/erpnext/selling/report/address_and_contacts/address_and_contacts.js index ef87586f66..c16d9752f5 100644 --- a/erpnext/selling/report/address_and_contacts/address_and_contacts.js +++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Address And Contacts"] = { "filters": [ diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js index d333c8be65..2aac3436c0 100644 --- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Customer-wise Item Price"] = { "filters": [ diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js index 073be78979..f63d02ec84 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js @@ -1,6 +1,6 @@ // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Item-wise Sales History"] = { "filters": [ diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js index 990d736baa..0203a054f9 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js @@ -1,6 +1,6 @@ // Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + function get_filters() { let filters = [ @@ -116,7 +116,7 @@ frappe.query_reports["Payment Terms Status for Sales Order"] = { "filters": get_filters(), "formatter": function(value, row, column, data, default_formatter){ if(column.fieldname == 'invoices' && value) { - invoices = value.split(','); + let invoices = value.split(','); const invoice_formatter = (prev_value, curr_value) => { if(prev_value != "") { return prev_value + ", " + default_formatter(curr_value, row, column, data); @@ -128,7 +128,7 @@ frappe.query_reports["Payment Terms Status for Sales Order"] = { return invoices.reduce(invoice_formatter, "") } else if (column.fieldname == 'paid_amount' && value){ - formatted_value = default_formatter(value, row, column, data); + let formatted_value = default_formatter(value, row, column, data); if(value > 0) { formatted_value = "" + formatted_value + "" } diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.js b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.js index 37634efb6c..204205949e 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.js +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Pending SO Items For Purchase Request"] = { } diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js index 87dd02f991..ace16f4f41 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.js +++ b/erpnext/selling/report/sales_analytics/sales_analytics.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Analytics"] = { "filters": [ diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index f3f931edfd..ac3d3dbf71 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Order Analysis"] = { "filters": [ diff --git a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js index 63d930c145..f08780ae72 100644 --- a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js +++ b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Partner Commission Summary"] = { "filters": [ diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js index adae47b87d..83934fb359 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Partner Target Variance based on Item Group"] = { "filters": [ diff --git a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js index e404233953..e443ab39f6 100644 --- a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js +++ b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Partner Transaction Summary"] = { "filters": [ diff --git a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js index ba6ee784b9..306ef6fffd 100644 --- a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js +++ b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Person Commission Summary"] = { "filters": [ diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js index 2b8443627d..9414ad6a8f 100644 --- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Sales Person Target Variance Based On Item Group"] = { "filters": [ diff --git a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js index 9f3d255e66..12bb49fd18 100644 --- a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Territory Target Variance Based On Item Group"] = { "filters": [ diff --git a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.js b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.js index bef800f104..c755a75d82 100644 --- a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.js +++ b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Territory-wise Sales"] = { diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js index d7c50a6697..a032285124 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["COGS By Item Group"] = { diff --git a/erpnext/stock/report/delayed_item_report/delayed_item_report.js b/erpnext/stock/report/delayed_item_report/delayed_item_report.js index 40e6abefeb..cf6e12ff47 100644 --- a/erpnext/stock/report/delayed_item_report/delayed_item_report.js +++ b/erpnext/stock/report/delayed_item_report/delayed_item_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Delayed Item Report"] = { "filters": [ diff --git a/erpnext/stock/report/delayed_order_report/delayed_order_report.js b/erpnext/stock/report/delayed_order_report/delayed_order_report.js index aab0f3d0d1..cf489c932e 100644 --- a/erpnext/stock/report/delayed_order_report/delayed_order_report.js +++ b/erpnext/stock/report/delayed_order_report/delayed_order_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Delayed Order Report"] = { "filters": [ diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js index 0b8f49653d..bc86979132 100644 --- a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js +++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js @@ -1,6 +1,6 @@ // Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + const DIFFERNCE_FIELD_NAMES = [ "fifo_qty_diff", diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js index bf11277d9c..0f9120ba2f 100644 --- a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js +++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Incorrect Balance Qty After Transaction"] = { "filters": [ diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js index c62d48081c..6325cc85c4 100644 --- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js +++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Incorrect Serial No Valuation"] = { "filters": [ diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js index ff424807e3..174d03317d 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Incorrect Stock Value Report"] = { "filters": [ diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.js b/erpnext/stock/report/item_price_stock/item_price_stock.js index 7af1dab6a0..c4684daa9f 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.js +++ b/erpnext/stock/report/item_price_stock/item_price_stock.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Item Price Stock"] = { "filters": [ diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.js b/erpnext/stock/report/item_shortage_report/item_shortage_report.js index ca42a331e9..5642038f3f 100644 --- a/erpnext/stock/report/item_shortage_report/item_shortage_report.js +++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Item Shortage Report"] = { "filters": [ diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.js b/erpnext/stock/report/item_variant_details/item_variant_details.js index 78eab4050c..b9022948f4 100644 --- a/erpnext/stock/report/item_variant_details/item_variant_details.js +++ b/erpnext/stock/report/item_variant_details/item_variant_details.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Item Variant Details"] = { "filters": [ diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js index 976e5156ad..fe977c6d5c 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Serial No Ledger"] = { "filters": [ diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js index 78afe6d264..ea7bf5688e 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.js +++ b/erpnext/stock/report/stock_analytics/stock_analytics.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Stock Analytics"] = { "filters": [ @@ -93,11 +93,11 @@ frappe.query_reports["Stock Analytics"] = { checkboxColumn: true, events: { onCheckRow: function(data) { - row_name = data[2].content; - row_values = data.slice(7).map(function (column) { + let row_name = data[2].content; + let row_values = data.slice(7).map(function (column) { return column.content; }) - entry = { + let entry = { 'name':row_name, 'values':row_values } diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.js b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.js index 254f5273be..ffef11a20e 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.js +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Stock and Account Value Comparison"] = { "filters": [ diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js index 31f389f236..3447e0a6fa 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + const DIFFERNCE_FIELD_NAMES = [ "difference_in_qty", diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js index 2a0fd4025c..7a48798495 100644 --- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js +++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Stock Qty vs Serial No Count"] = { "filters": [ diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js index 88054aaea7..3d247f6740 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Total Stock Summary"] = { "filters": [ diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js index 39cfd7274f..8d6b283a15 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Warehouse wise Item Balance Age and Value"] = { "filters": [ diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js index 752e464e27..4a77052a90 100644 --- a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js +++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js @@ -1,6 +1,6 @@ // Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Warehouse Wise Stock Balance"] = { "filters": [ diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js index 18691fe264..a133770f6d 100644 --- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js +++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["First Response Time for Issues"] = { "filters": [ diff --git a/erpnext/support/report/issue_analytics/issue_analytics.js b/erpnext/support/report/issue_analytics/issue_analytics.js index 746eee025a..be45b9b44a 100644 --- a/erpnext/support/report/issue_analytics/issue_analytics.js +++ b/erpnext/support/report/issue_analytics/issue_analytics.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Issue Analytics"] = { "filters": [ @@ -93,11 +93,11 @@ frappe.query_reports["Issue Analytics"] = { events: { onCheckRow: function(data) { if (data && data.length) { - row_name = data[2].content; - row_values = data.slice(3).map(function(column) { + let row_name = data[2].content; + let row_values = data.slice(3).map(function(column) { return column.content; }) - entry = { + let entry = { 'name': row_name, 'values': row_values } diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js index a5122d03ad..aee6f53f17 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Issue Summary"] = { "filters": [ diff --git a/erpnext/support/report/support_hour_distribution/support_hour_distribution.js b/erpnext/support/report/support_hour_distribution/support_hour_distribution.js index ae30b6a550..82ccc73bf9 100644 --- a/erpnext/support/report/support_hour_distribution/support_hour_distribution.js +++ b/erpnext/support/report/support_hour_distribution/support_hour_distribution.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["Support Hour Distribution"] = { "filters": [ diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.js b/erpnext/utilities/report/youtube_interactions/youtube_interactions.js index 6e3e4e6980..adf2cf62f7 100644 --- a/erpnext/utilities/report/youtube_interactions/youtube_interactions.js +++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.js @@ -1,6 +1,6 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* eslint-disable */ + frappe.query_reports["YouTube Interactions"] = { "filters": [ From 3fa2a8c2d8f4e8e25727b64fe85b65a8724907fa Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 15 Jul 2023 18:03:16 +0530 Subject: [PATCH 065/107] chore: fix linting issues --- .../doctype/journal_entry/journal_entry.js | 8 +- .../doctype/payment_order/payment_order.js | 2 +- .../sales_payment_summary.js | 1 - erpnext/assets/doctype/asset/asset.js | 2 +- .../supplier_scorecard_period.js | 3 - .../supplier_scorecard_standing.js | 1 - .../supplier_scorecard_variable.js | 2 - erpnext/crm/doctype/lead/lead.js | 5 +- .../doctype/work_order/work_order.js | 9 +- erpnext/public/js/agriculture/ternary_plot.js | 232 ------------------ erpnext/public/js/stock_analytics.js | 9 +- erpnext/public/js/stock_grid_report.js | 21 +- .../page/point_of_sale/pos_item_cart.js | 2 +- erpnext/stock/doctype/batch/batch_list.js | 2 +- .../doctype/delivery_trip/delivery_trip.js | 2 +- .../stock_reconciliation.js | 4 +- erpnext/templates/includes/product_list.js | 10 +- erpnext/templates/pages/projects.js | 2 +- 18 files changed, 42 insertions(+), 275 deletions(-) delete mode 100644 erpnext/public/js/agriculture/ternary_plot.js diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index a51e38eefe..8d8cbefa71 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -264,11 +264,11 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } if(jvd.party_type && jvd.party) { - var party_field = ""; + let party_field = ""; if(jvd.reference_type.indexOf("Sales")===0) { - var party_field = "customer"; + party_field = "customer"; } else if (jvd.reference_type.indexOf("Purchase")===0) { - var party_field = "supplier"; + party_field = "supplier"; } if (party_field) { @@ -368,7 +368,7 @@ cur_frm.cscript.update_totals = function(doc) { td += flt(accounts[i].debit, precision("debit", accounts[i])); tc += flt(accounts[i].credit, precision("credit", accounts[i])); } - var doc = locals[doc.doctype][doc.name]; + doc = locals[doc.doctype][doc.name]; doc.total_debit = td; doc.total_credit = tc; doc.difference = flt((td - tc), precision("difference")); diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index 7d85d89c45..6630e7122c 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -124,7 +124,7 @@ frappe.ui.form.on('Payment Order', { return frappe.call({ method: "erpnext.accounts.doctype.payment_order.payment_order.make_payment_records", args: { - "name": me.frm.doc.name, + "name": frm.doc.name, "supplier": args.supplier, "mode_of_payment": args.mode_of_payment }, diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js index 44e20e83c5..92e6f7f514 100644 --- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js +++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js @@ -29,7 +29,6 @@ frappe.query_reports["Sales Payment Summary"] = { "label": __("Owner"), "fieldtype": "Link", "options": "User", - "defaults": user }, { "fieldname":"is_pos", diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 43920adca3..97a41de526 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -473,7 +473,7 @@ frappe.ui.form.on('Asset', { } const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code); if (!item) { - doctype_field = frappe.scrub(doctype) + let doctype_field = frappe.scrub(doctype) frm.set_value(doctype_field, ''); frappe.msgprint({ title: __('Invalid {0}', [__(doctype)]), diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js index a4cdeb3195..62079cc3e0 100644 --- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js +++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js @@ -1,9 +1,6 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* global frappe */ - - frappe.ui.form.on("Supplier Scorecard Period", { onload: function(frm) { let criteria_grid = frm.get_field("criteria").grid; diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js index dccfcc34bb..22756e75cf 100644 --- a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js +++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js @@ -1,7 +1,6 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* global frappe */ frappe.ui.form.on("Supplier Scorecard Standing", { refresh: function() { diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js index 2d74fdd190..b3b4321a88 100644 --- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js @@ -1,8 +1,6 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* global frappe */ - frappe.ui.form.on("Supplier Scorecard Variable", { refresh: function() { diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 9ac54183a2..b1799cee6c 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -54,6 +54,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } add_lead_to_prospect () { + let me = this; frappe.prompt([ { fieldname: 'prospect', @@ -67,12 +68,12 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller frappe.call({ method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect', args: { - 'lead': cur_frm.doc.name, + 'lead': me.frm.doc.name, 'prospect': data.prospect }, callback: function(r) { if (!r.exc) { - frm.reload_doc(); + me.frm.reload_doc(); } }, freeze: true, diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index c1a078d65e..58945bba77 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -624,7 +624,7 @@ erpnext.work_order = { if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { frm.has_finish_btn = true; - var finish_btn = frm.add_custom_button(__('Finish'), function() { + let finish_btn = frm.add_custom_button(__('Finish'), function() { erpnext.work_order.make_se(frm, 'Manufacture'); }); @@ -648,7 +648,7 @@ erpnext.work_order = { } } else { if ((flt(doc.produced_qty) < flt(doc.qty))) { - var finish_btn = frm.add_custom_button(__('Finish'), function() { + let finish_btn = frm.add_custom_button(__('Finish'), function() { erpnext.work_order.make_se(frm, 'Manufacture'); }); finish_btn.addClass('btn-primary'); @@ -756,13 +756,14 @@ erpnext.work_order = { }, make_consumption_se: function(frm, backflush_raw_materials_based_on) { + let max = 0; if(!frm.doc.skip_transfer){ - var max = (backflush_raw_materials_based_on === "Material Transferred for Manufacture") ? + max = (backflush_raw_materials_based_on === "Material Transferred for Manufacture") ? flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) : flt(frm.doc.qty) - flt(frm.doc.produced_qty); // flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing); } else { - var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty); + max = flt(frm.doc.qty) - flt(frm.doc.produced_qty); } frappe.call({ diff --git a/erpnext/public/js/agriculture/ternary_plot.js b/erpnext/public/js/agriculture/ternary_plot.js deleted file mode 100644 index b06a1fd7c8..0000000000 --- a/erpnext/public/js/agriculture/ternary_plot.js +++ /dev/null @@ -1,232 +0,0 @@ -frappe.provide('agriculture'); - -agriculture.TernaryPlot = class TernaryPlot { - constructor(opts) { - Object.assign(this, opts); - - frappe.require('assets/frappe/js/lib/snap.svg-min.js', () => { - this.make_svg(); - this.init_snap(); - this.init_config(); - this.make_plot(); - this.make_plot_marking(); - this.make_legend(); - this.mark_blip(); - }); - } - - make_svg() { - this.$svg = $(''); - $(this.parent).append(this.$svg); - } - - init_snap() { - this.paper = new Snap(this.$svg.get(0)); - } - - init_config() { - this.config = { - triangle_side: 300, - spacing: 50, - strokeWidth: 1, - stroke: frappe.ui.color.get('black') - }; - this.config.scaling_factor = this.config.triangle_side / 100; - let { triangle_side: t, spacing: s, scaling_factor: p } = this.config; - - this.coords = { - sand: { - points: [ - s + t * Snap.cos(60), s, - s, s + t * Snap.cos(30), - s + t, s + t * Snap.cos(30) - ], - color: frappe.ui.color.get('peach') - }, - loamy_sand: { - points: [ - s + 15 * p * Snap.cos(60), s + (100 - 15) * p * Snap.cos(30), - s + 10 * p * Snap.cos(60), s + (100 - 10) * p * Snap.cos(30), - s + (100 - 85) * p, s + t * Snap.cos(30), - s + (100 - 70) * p, s + t * Snap.cos(30) - ], - color: frappe.ui.color.get('pink') - }, - sandy_loam: { - points: [ - s + 20 * p * Snap.cos(60) + 27.5 * p, s + (100 - 20) * p * Snap.cos(30), - s + 20 * p * Snap.cos(60), s + (100 - 20) * p * Snap.cos(30), - s + 15 * p * Snap.cos(60), s + (100 - 15) * p * Snap.cos(30), - s + (100 - 75) * p, s + t * Snap.cos(30), - s + (100 - 50) * p, s + t * Snap.cos(30), - s + (100 - 50) * p + 7.5 * p * Snap.cos(60), s + t * Snap.cos(30) - 7.5 * p * Snap.cos(30), - s + (100 - 50) * p + 7.5 * p * Snap.cos(60) - 10 * p, s + t * Snap.cos(30) - 7.5 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('pink', 'light') - }, - loam: { - points: [ - s + (100 - 50) * p + 27.5 * p * Snap.cos(60), s + t * Snap.cos(30) - 27.5 * p * Snap.cos(30), - s + (100 - 50) * p + 27.5 * p * Snap.cos(60) - 22.5 * p, s + t * Snap.cos(30) - 27.5 * p * Snap.cos(30), - s + 20 * p * Snap.cos(60) + 27.5 * p, s + (100 - 20) * p * Snap.cos(30), - s + (100 - 50) * p + 7.5 * p * Snap.cos(60) - 10 * p, s + t * Snap.cos(30) - 7.5 * p * Snap.cos(30), - s + (100 - 50) * p + 7.5 * p * Snap.cos(60), s + t * Snap.cos(30) - 7.5 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('brown') - }, - silt_loam: { - points: [ - s + t - 27.5 * p * Snap.cos(60), s + 72.5 * p * Snap.cos(30), - s + (100 - 50) * p + 27.5 * p * Snap.cos(60), s + t * Snap.cos(30) - 27.5 * p * Snap.cos(30), - s + (100 - 50) * p, s + t * Snap.cos(30), - s + (100 - 20) * p, s + t * Snap.cos(30), - s + (100 - 20) * p + 12.5 * p * Snap.cos(60), s + 90 * p * Snap.cos(30), - s + t - 12.5 * p * Snap.cos(60), s + (100 - 12.5) * p * Snap.cos(30) - ], - color: frappe.ui.color.get('green', 'dark') - }, - silt: { - points: [ - s + t - 12.5 * p * Snap.cos(60), s + (100 - 12.5) * p * Snap.cos(30), - s + (100 - 20) * p + 12.5 * p * Snap.cos(60), s + 90 * p * Snap.cos(30), - s + (100 - 20) * p, s + t * Snap.cos(30), - s + t, s + t * Snap.cos(30) - ], - color: frappe.ui.color.get('green') - }, - silty_clay_loam: { - points: [ - s + t - 40 * p * Snap.cos(60), s + 60 * p * Snap.cos(30), - s + t - 40 * p * Snap.cos(60) - 20 * p, s + 60 * p * Snap.cos(30), - s + t - 27.5 * p * Snap.cos(60) - 20 * p, s + 72.5 * p * Snap.cos(30), - s + t - 27.5 * p * Snap.cos(60), s + 72.5 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('cyan', 'dark') - }, - silty_clay: { - points: [ - s + t - 60 * p * Snap.cos(60), s + 40 * p * Snap.cos(30), - s + t - 40 * p * Snap.cos(60) - 20 * p, s + 60 * p * Snap.cos(30), - s + t - 40 * p * Snap.cos(60), s + 60 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('cyan') - }, - clay_loam: { - points: [ - s + t - 40 * p * Snap.cos(60) - 20 * p, s + 60 * p * Snap.cos(30), - s + t - 40 * p * Snap.cos(60) - 45 * p, s + 60 * p * Snap.cos(30), - s + t - 27.5 * p * Snap.cos(60) - 45 * p, s + 72.5 * p * Snap.cos(30), - s + t - 27.5 * p * Snap.cos(60) - 20 * p, s + 72.5 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('green', 'light') - }, - sandy_clay_loam: { - points: [ - s + 35 * p * Snap.cos(60) + 20 * p, s + (100 - 35) * p * Snap.cos(30), - s + 35 * p * Snap.cos(60), s + (100 - 35) * p * Snap.cos(30), - s + 20 * p * Snap.cos(60), s + (100 - 20) * p * Snap.cos(30), - s + 20 * p * Snap.cos(60) + 27.5 * p, s + (100 - 20) * p * Snap.cos(30), - s + t - 27.5 * p * Snap.cos(60) - 45 * p, s + 72.5 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('pink', 'dark') - }, - sandy_clay: { - points: [ - s + 55 * p * Snap.cos(60), s + (100 - 55) * p * Snap.cos(30), - s + 35 * p * Snap.cos(60), s + (100 - 35) * p * Snap.cos(30), - s + 35 * p * Snap.cos(60) + 20 * p, s + (100 - 35) * p * Snap.cos(30) - ], - color: frappe.ui.color.get('red') - }, - clay: { - points: [ - s + t * Snap.cos(60), s, - s + 55 * p * Snap.cos(60), s + (100 - 55) * p * Snap.cos(30), - s + t - 40 * p * Snap.cos(60) - 45 * p, s + 60 * p * Snap.cos(30), - s + t - 40 * p * Snap.cos(60) - 20 * p, s + 60 * p * Snap.cos(30), - s + t - 60 * p * Snap.cos(60), s + 40 * p * Snap.cos(30) - ], - color: frappe.ui.color.get('yellow') - }, - }; - } - - get_coords(soil_type) { - return this.coords[soil_type].points; - } - - get_color(soil_type) { - return this.coords[soil_type].color; - } - - make_plot() { - for (let soil_type in this.coords) { - this.paper.polygon(this.get_coords(soil_type)).attr({ - fill: this.get_color(soil_type), - stroke: this.config.stroke, - strokeWidth: this.config.strokeWidth - }); - } - } - - make_plot_marking() { - let { triangle_side: t, spacing: s, scaling_factor: p } = this.config; - - let clay = this.paper.text(t * Snap.cos(60) / 2, s + t * Snap.cos(30) / 2, __("Clay")).attr({ - fill: frappe.ui.color.get('black') - }); - clay.transform("r300"); - - let silt = this.paper.text(t, s + t * Snap.cos(30) / 2, __("Silt")).attr({ - fill: frappe.ui.color.get('black') - }); - silt.transform("r60"); - - let sand = this.paper.text(35 + t * Snap.cos(60), 90 + t * Snap.cos(30), __("Sand")).attr({ - fill: frappe.ui.color.get('black') - }); - sand.transform("r0"); - } - - make_legend() { - // let side = len(this.coords)/2; - let index = 1; - let offset = 0; - let exec_once = true; - for (let soil_type in this.coords) { - if (index > 6 && exec_once){ - offset = 300; - index = 1; - exec_once = false; - } - let rect = this.paper.rect(0+offset, 0+index*20, 100, 19, 5, 5).attr({ - fill: this.get_color(soil_type), - stroke: frappe.ui.color.get('black') - }); - let text = this.paper.text(5+offset, 16+index*20, soil_type).attr({ - fill: frappe.ui.color.get('black'), - 'font-size': 12 - }); - index++; - } - } - - mark_blip({clay, sand, silt} = this) { - if (clay + sand + silt != 0){ - let { triangle_side: t, spacing: s, scaling_factor: p } = this.config; - - let x_blip = s + clay * p * Snap.cos(60) + silt * p; - let y_blip = s + silt * p * Snap.cos(30) + sand * p * Snap.sin(60); - this.blip = this.paper.circle(x_blip, y_blip, 4).attr({ - fill: frappe.ui.color.get("orange"), - stroke: frappe.ui.color.get("orange"), - strokeWidth: 2 - }); - } - } - - remove_blip() { - if (typeof this.blip !== 'undefined') - this.blip.remove(); - } -}; diff --git a/erpnext/public/js/stock_analytics.js b/erpnext/public/js/stock_analytics.js index a343c3402a..98e7f7849e 100644 --- a/erpnext/public/js/stock_analytics.js +++ b/erpnext/public/js/stock_analytics.js @@ -125,6 +125,7 @@ erpnext.StockAnalytics = class StockAnalytics extends erpnext.StockGridReport { this.serialized_buying_rates = this.get_serialized_buying_rates(); for(var i=0, j=data.length; i 0) { // incoming - rate is given - var rate = sl.incoming_rate; - var add_qty = sl.qty; + let rate = sl.incoming_rate; + let add_qty = sl.qty; if(wh.balance_qty < 0) { // negative valuation // only add value of quantity if @@ -25,10 +27,11 @@ erpnext.StockGridReport = class StockGridReport extends frappe.views.TreeGridRep add_qty = 0; } } + if(sl.serial_no) { - var value_diff = this.get_serialized_value_diff(sl); + value_diff = this.get_serialized_value_diff(sl); } else { - var value_diff = (rate * add_qty); + value_diff = (rate * add_qty); } if(add_qty) @@ -39,19 +42,19 @@ erpnext.StockGridReport = class StockGridReport extends frappe.views.TreeGridRep // outgoing if(sl.serial_no) { - var value_diff = -1 * this.get_serialized_value_diff(sl); + value_diff = -1 * this.get_serialized_value_diff(sl); } else if(is_fifo) { - var value_diff = fifo_value_diff; + value_diff = fifo_value_diff; } else { // average rate for weighted average - var rate = (wh.balance_qty.toFixed(2) == 0.00 ? 0 : + let rate = (wh.balance_qty.toFixed(2) == 0.00 ? 0 : flt(wh.balance_value) / flt(wh.balance_qty)); // no change in value if negative qty if((wh.balance_qty + sl.qty).toFixed(2) >= 0.00) - var value_diff = (rate * sl.qty); + value_diff = (rate * sl.qty); else - var value_diff = -wh.balance_value; + value_diff = -wh.balance_value; } } diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 12cc629776..46490c43ae 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -945,7 +945,7 @@ erpnext.PointOfSale.ItemCart = class { `
No recent transactions found
` ) return; - }; + } const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow(); this.$customer_section.find('.customer-desc').html(`Last transacted ${elapsed_time}`); diff --git a/erpnext/stock/doctype/batch/batch_list.js b/erpnext/stock/doctype/batch/batch_list.js index 0de9fd0150..f1a0643cfd 100644 --- a/erpnext/stock/doctype/batch/batch_list.js +++ b/erpnext/stock/doctype/batch/batch_list.js @@ -9,6 +9,6 @@ frappe.listview_settings['Batch'] = { return [__("Expired"), "red", "expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0"] } else { return [__("Active"), "green", "batch_qty,>,0|disabled,=,0"]; - }; + } } }; diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index a6fbb66aa2..de503dc73f 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -92,7 +92,7 @@ frappe.ui.form.on('Delivery Trip', { frm.set_value("driver_email", data.message.email); } }); - }; + } }, optimize_route: function (frm) { diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 0664c2929c..564562d722 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -67,6 +67,7 @@ frappe.ui.form.on("Stock Reconciliation", { }, company: function(frm) { + frm.trigger("toggle_display_account_head"); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, @@ -221,9 +222,6 @@ frappe.ui.form.on("Stock Reconciliation", { frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount)); } }, - company: function(frm) { - frm.trigger("toggle_display_account_head"); - }, toggle_display_account_head: function(frm) { frm.toggle_display(['expense_account', 'cost_center'], erpnext.is_perpetual_inventory_enabled(frm.doc.company)); diff --git a/erpnext/templates/includes/product_list.js b/erpnext/templates/includes/product_list.js index 2f9d978eb1..acee36c80c 100644 --- a/erpnext/templates/includes/product_list.js +++ b/erpnext/templates/includes/product_list.js @@ -27,10 +27,10 @@ window.get_product_list = function() { } window.render_product_list = function(data) { - var table = $("#search-list .table"); + let table = $("#search-list .table"); if(data.length) { if(!table.length) - var table = $("").appendTo("#search-list"); + table = $("
").appendTo("#search-list"); $.each(data, function(i, d) { $(d).appendTo(table); @@ -38,11 +38,13 @@ window.render_product_list = function(data) { } if(data.length < 10) { if(!table) { + let message = __("No products found."); $(".more-btn") - .replaceWith("
{{ _("No products found.") }}
"); + .replaceWith(`
{{ ${message} }}
`); } else { + let message = __("Nothing more to show."); $(".more-btn") - .replaceWith("
{{ _("Nothing more to show.") }}
"); + .replaceWith(`
{{ ${message} }}
`); } } else { $(".more-btn").toggle(true) diff --git a/erpnext/templates/pages/projects.js b/erpnext/templates/pages/projects.js index bd6bcea7ca..7149ceea10 100644 --- a/erpnext/templates/pages/projects.js +++ b/erpnext/templates/pages/projects.js @@ -72,7 +72,7 @@ frappe.ready(function() { var more_items = function(item, item_status){ if(item_status) { - var item_status = $('.project-'+ item +'-section .btn-group .bold').hasClass('btn-completed-'+ item) + item_status = $('.project-'+ item +'-section .btn-group .bold').hasClass('btn-completed-'+ item) ? 'completed' : 'open'; } $.ajax({ From a6ce20a0fc7e6393e3795ca65025fb66f7450517 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 15 Jul 2023 19:40:53 +0530 Subject: [PATCH 066/107] chore: Remove domain related files --- erpnext/public/build.json | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 1bed541831..b9b48aba45 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -26,7 +26,6 @@ "public/js/templates/item_selector.html", "public/js/utils/item_selector.js", "public/js/help_links.js", - "public/js/agriculture/ternary_plot.js", "public/js/templates/item_quick_entry.html", "public/js/utils/customer_quick_entry.js", "public/js/utils/supplier_quick_entry.js", From ef19634a13d8be1eda7de5d0ca441d8ffc71f669 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Jul 2023 12:25:56 +0530 Subject: [PATCH 067/107] chore: fix build --- erpnext/public/js/erpnext.bundle.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 4e028e4c31..85a2e242db 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -10,7 +10,6 @@ import "./controllers/transaction"; import "./templates/item_selector.html"; import "./utils/item_selector"; import "./help_links"; -import "./agriculture/ternary_plot"; import "./templates/item_quick_entry.html"; import "./utils/contact_address_quick_entry"; import "./utils/customer_quick_entry"; From 43e65d91ea4abd74187835b0cb5b967e75e163bd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Jul 2023 21:26:40 +0530 Subject: [PATCH 068/107] chore: Remove unused files --- erpnext/public/js/account_tree_grid.js | 238 ------- erpnext/public/js/leaflet/leaflet.draw.js | 143 ---- erpnext/public/js/leaflet/leaflet.js | 771 ---------------------- 3 files changed, 1152 deletions(-) delete mode 100644 erpnext/public/js/account_tree_grid.js delete mode 100755 erpnext/public/js/leaflet/leaflet.draw.js delete mode 100755 erpnext/public/js/leaflet/leaflet.js diff --git a/erpnext/public/js/account_tree_grid.js b/erpnext/public/js/account_tree_grid.js deleted file mode 100644 index 413a5ee971..0000000000 --- a/erpnext/public/js/account_tree_grid.js +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -erpnext.AccountTreeGrid = class AccountTreeGrid extends frappe.views.TreeGridReport { - constructor(wrapper, title) { - super({ - title: title, - parent: $(wrapper).find('.layout-main'), - page: wrapper.page, - doctypes: ["Company", "Fiscal Year", "Account", "GL Entry", "Cost Center"], - tree_grid: { - show: true, - parent_field: "parent_account", - formatter: function(item) { - return repl("\ - %(value)s", { - value: item.name, - }); - } - }, - }); - - this.filters = [ - {fieldtype: "Select", label: __("Company"), link:"Company", fieldname: "company", - default_value: __("Select Company..."), - filter: function(val, item, opts, me) { - if (item.company == val || val == opts.default_value) { - return me.apply_zero_filter(val, item, opts, me); - } - return false; - }}, - {fieldtype: "Select", label: "Fiscal Year", link:"Fiscal Year", fieldname: "fiscal_year", - default_value: __("Select Fiscal Year...")}, - {fieldtype: "Date", label: __("From Date"), fieldname: "from_date"}, - {fieldtype: "Label", label: __("To")}, - {fieldtype: "Date", label: __("To Date"), fieldname: "to_date"} - ] - } - setup_columns() { - this.columns = [ - {id: "name", name: __("Account"), field: "name", width: 300, cssClass: "cell-title"}, - {id: "opening_dr", name: __("Opening (Dr)"), field: "opening_dr", width: 100, - formatter: this.currency_formatter}, - {id: "opening_cr", name: __("Opening (Cr)"), field: "opening_cr", width: 100, - formatter: this.currency_formatter}, - {id: "debit", name: __("Debit"), field: "debit", width: 100, - formatter: this.currency_formatter}, - {id: "credit", name: __("Credit"), field: "credit", width: 100, - formatter: this.currency_formatter}, - {id: "closing_dr", name: __("Closing (Dr)"), field: "closing_dr", width: 100, - formatter: this.currency_formatter}, - {id: "closing_cr", name: __("Closing (Cr)"), field: "closing_cr", width: 100, - formatter: this.currency_formatter} - ]; - } - - setup_filters() { - super.setup_filters(); - var me = this; - // default filters - this.filter_inputs.fiscal_year.change(function() { - var fy = $(this).val(); - $.each(frappe.report_dump.data["Fiscal Year"], function(i, v) { - if (v.name==fy) { - me.filter_inputs.from_date.val(frappe.datetime.str_to_user(v.year_start_date)); - me.filter_inputs.to_date.val(frappe.datetime.str_to_user(v.year_end_date)); - } - }); - me.refresh(); - }); - me.show_zero_check() - if(me.ignore_closing_entry) me.ignore_closing_entry(); - } - prepare_data() { - var me = this; - if(!this.primary_data) { - // make accounts list - me.data = []; - me.parent_map = {}; - me.item_by_name = {}; - - $.each(frappe.report_dump.data["Account"], function(i, v) { - var d = copy_dict(v); - - me.data.push(d); - me.item_by_name[d.name] = d; - if(d.parent_account) { - me.parent_map[d.name] = d.parent_account; - } - }); - - me.primary_data = [].concat(me.data); - } - - me.data = [].concat(me.primary_data); - $.each(me.data, function(i, d) { - me.init_account(d); - }); - - this.set_indent(); - this.prepare_balances(); - - } - init_account(d) { - this.reset_item_values(d); - } - - prepare_balances() { - var gl = frappe.report_dump.data['GL Entry']; - var me = this; - - this.opening_date = frappe.datetime.user_to_obj(this.filter_inputs.from_date.val()); - this.closing_date = frappe.datetime.user_to_obj(this.filter_inputs.to_date.val()); - this.set_fiscal_year(); - if (!this.fiscal_year) return; - - $.each(this.data, function(i, v) { - v.opening_dr = v.opening_cr = v.debit - = v.credit = v.closing_dr = v.closing_cr = 0; - }); - - $.each(gl, function(i, v) { - var posting_date = frappe.datetime.str_to_obj(v.posting_date); - var account = me.item_by_name[v.account]; - me.update_balances(account, posting_date, v); - }); - - this.update_groups(); - } - update_balances(account, posting_date, v) { - // opening - if (posting_date < this.opening_date || v.is_opening === "Yes") { - if (account.report_type === "Profit and Loss" && - posting_date <= frappe.datetime.str_to_obj(this.fiscal_year[1])) { - // balance of previous fiscal_year should - // not be part of opening of pl account balance - } else { - var opening_bal = flt(account.opening_dr) - flt(account.opening_cr) + - flt(v.debit) - flt(v.credit); - this.set_debit_or_credit(account, "opening", opening_bal); - } - } else if (this.opening_date <= posting_date && posting_date <= this.closing_date) { - // in between - account.debit += flt(v.debit); - account.credit += flt(v.credit); - } - // closing - var closing_bal = flt(account.opening_dr) - flt(account.opening_cr) + - flt(account.debit) - flt(account.credit); - this.set_debit_or_credit(account, "closing", closing_bal); - } - set_debit_or_credit(account, field, balance) { - if(balance > 0) { - account[field+"_dr"] = balance; - account[field+"_cr"] = 0; - } else { - account[field+"_cr"] = Math.abs(balance); - account[field+"_dr"] = 0; - } - } - update_groups() { - // update groups - var me= this; - $.each(this.data, function(i, account) { - // update groups - if((account.is_group == 0) || (account.rgt - account.lft == 1)) { - var parent = me.parent_map[account.name]; - while(parent) { - var parent_account = me.item_by_name[parent]; - $.each(me.columns, function(c, col) { - if (col.formatter == me.currency_formatter) { - if(col.field=="opening_dr") { - var bal = flt(parent_account.opening_dr) - - flt(parent_account.opening_cr) + - flt(account.opening_dr) - flt(account.opening_cr); - me.set_debit_or_credit(parent_account, "opening", bal); - } else if(col.field=="closing_dr") { - var bal = flt(parent_account.closing_dr) - - flt(parent_account.closing_cr) + - flt(account.closing_dr) - flt(account.closing_cr); - me.set_debit_or_credit(parent_account, "closing", bal); - } else if(in_list(["debit", "credit"], col.field)) { - parent_account[col.field] = flt(parent_account[col.field]) + - flt(account[col.field]); - } - } - }); - parent = me.parent_map[parent]; - } - } - }); - } - - set_fiscal_year() { - if (this.opening_date > this.closing_date) { - frappe.msgprint(__("Opening Date should be before Closing Date")); - return; - } - - this.fiscal_year = null; - var me = this; - $.each(frappe.report_dump.data["Fiscal Year"], function(i, v) { - if (me.opening_date >= frappe.datetime.str_to_obj(v.year_start_date) && - me.closing_date <= frappe.datetime.str_to_obj(v.year_end_date)) { - me.fiscal_year = v; - } - }); - - if (!this.fiscal_year) { - frappe.msgprint(__("Opening Date and Closing Date should be within same Fiscal Year")); - return; - } - } - - show_general_ledger(account) { - frappe.route_options = { - account: account, - company: this.company, - from_date: this.from_date, - to_date: this.to_date - }; - frappe.set_route("query-report", "General Ledger"); - } -}; diff --git a/erpnext/public/js/leaflet/leaflet.draw.js b/erpnext/public/js/leaflet/leaflet.draw.js deleted file mode 100755 index 26f1e19da5..0000000000 --- a/erpnext/public/js/leaflet/leaflet.draw.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - Leaflet.draw, a plugin that adds drawing and editing tools to Leaflet powered maps. - (c) 2012-2013, Jacob Toye, Smartrak - - https://github.com/Leaflet/Leaflet.draw - http://leafletjs.com - https://github.com/jacobtoye -*/ -! function(t, e) { - L.drawVersion = "0.2.3", L.drawLocal = { draw: { toolbar: { actions: { title: "Cancel drawing", text: "Cancel" }, undo: { title: "Delete last point drawn", text: "Delete last point" }, buttons: { polyline: "Draw a polyline", polygon: "Draw a polygon", rectangle: "Draw a rectangle", circle: "Draw a circle", marker: "Draw a marker" } }, handlers: { circle: { tooltip: { start: "Click and drag to draw circle." } }, marker: { tooltip: { start: "Click map to place marker." } }, polygon: { tooltip: { start: "Click to start drawing shape.", cont: "Click to continue drawing shape.", end: "Click first point to close this shape." } }, polyline: { error: "Error: shape edges cannot cross!", tooltip: { start: "Click to start drawing line.", cont: "Click to continue drawing line.", end: "Click last point to finish line." } }, rectangle: { tooltip: { start: "Click and drag to draw rectangle." } }, simpleshape: { tooltip: { end: "Release mouse to finish drawing." } } } }, edit: { toolbar: { actions: { save: { title: "Save changes.", text: "Save" }, cancel: { title: "Cancel editing, discards all changes.", text: "Cancel" } }, buttons: { edit: "Edit layers.", editDisabled: "No layers to edit.", remove: "Delete layers.", removeDisabled: "No layers to delete." } }, handlers: { edit: { tooltip: { text: "Drag handles, or marker to edit feature.", subtext: "Click cancel to undo changes." } }, remove: { tooltip: { text: "Click on a feature to remove" } } } } }, L.Draw = {}, L.Draw.Feature = L.Handler.extend({ includes: L.Mixin.Events, initialize: function(t, e) { this._map = t, this._container = t._container, this._overlayPane = t._panes.overlayPane, this._popupPane = t._panes.popupPane, e && e.shapeOptions && (e.shapeOptions = L.Util.extend({}, this.options.shapeOptions, e.shapeOptions)), L.setOptions(this, e) }, enable: function() { this._enabled || (this.fire("enabled", { handler: this.type }), this._map.fire("draw:drawstart", { layerType: this.type }), L.Handler.prototype.enable.call(this)) }, disable: function() { this._enabled && (L.Handler.prototype.disable.call(this), this._map.fire("draw:drawstop", { layerType: this.type }), this.fire("disabled", { handler: this.type })) }, addHooks: function() { var t = this._map; - t && (L.DomUtil.disableTextSelection(), t.getContainer().focus(), this._tooltip = new L.Tooltip(this._map), L.DomEvent.on(this._container, "keyup", this._cancelDrawing, this)) }, removeHooks: function() { this._map && (L.DomUtil.enableTextSelection(), this._tooltip.dispose(), this._tooltip = null, L.DomEvent.off(this._container, "keyup", this._cancelDrawing, this)) }, setOptions: function(t) { L.setOptions(this, t) }, _fireCreatedEvent: function(t) { this._map.fire("draw:created", { layer: t, layerType: this.type }) }, _cancelDrawing: function(t) { 27 === t.keyCode && this.disable() } }), L.Draw.Polyline = L.Draw.Feature.extend({ statics: { TYPE: "polyline" }, Poly: L.Polyline, options: { allowIntersection: !0, repeatMode: !1, drawError: { color: "#b00b00", timeout: 2500 }, icon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: "leaflet-div-icon leaflet-editing-icon" }), guidelineDistance: 20, maxGuideLineLength: 4e3, shapeOptions: { stroke: !0, color: "#f06eaa", weight: 4, opacity: .5, fill: !1, clickable: !0 }, metric: !0, showLength: !0, zIndexOffset: 2e3 }, initialize: function(t, e) { this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error, e && e.drawError && (e.drawError = L.Util.extend({}, this.options.drawError, e.drawError)), this.type = L.Draw.Polyline.TYPE, L.Draw.Feature.prototype.initialize.call(this, t, e) }, addHooks: function() { L.Draw.Feature.prototype.addHooks.call(this), this._map && (this._markers = [], this._markerGroup = new L.LayerGroup, this._map.addLayer(this._markerGroup), this._poly = new L.Polyline([], this.options.shapeOptions), this._tooltip.updateContent(this._getTooltipText()), this._mouseMarker || (this._mouseMarker = L.marker(this._map.getCenter(), { icon: L.divIcon({ className: "leaflet-mouse-marker", iconAnchor: [20, 20], iconSize: [40, 40] }), opacity: 0, zIndexOffset: this.options.zIndexOffset })), this._mouseMarker.on("mousedown", this._onMouseDown, this).addTo(this._map), this._map.on("mousemove", this._onMouseMove, this).on("mouseup", this._onMouseUp, this).on("zoomend", this._onZoomEnd, this)) }, removeHooks: function() { L.Draw.Feature.prototype.removeHooks.call(this), this._clearHideErrorTimeout(), this._cleanUpShape(), this._map.removeLayer(this._markerGroup), delete this._markerGroup, delete this._markers, this._map.removeLayer(this._poly), delete this._poly, this._mouseMarker.off("mousedown", this._onMouseDown, this).off("mouseup", this._onMouseUp, this), this._map.removeLayer(this._mouseMarker), delete this._mouseMarker, this._clearGuides(), this._map.off("mousemove", this._onMouseMove, this).off("zoomend", this._onZoomEnd, this) }, deleteLastVertex: function() { if (!(this._markers.length <= 1)) { var t = this._markers.pop(), - e = this._poly, - i = this._poly.spliceLatLngs(e.getLatLngs().length - 1, 1)[0]; - this._markerGroup.removeLayer(t), e.getLatLngs().length < 2 && this._map.removeLayer(e), this._vertexChanged(i, !1) } }, addVertex: function(t) { var e = this._markers.length; return e > 0 && !this.options.allowIntersection && this._poly.newLatLngIntersects(t) ? void this._showErrorTooltip() : (this._errorShown && this._hideErrorTooltip(), this._markers.push(this._createMarker(t)), this._poly.addLatLng(t), 2 === this._poly.getLatLngs().length && this._map.addLayer(this._poly), void this._vertexChanged(t, !0)) }, _finishShape: function() { var t = this._poly.newLatLngIntersects(this._poly.getLatLngs()[0], !0); return !this.options.allowIntersection && t || !this._shapeIsValid() ? void this._showErrorTooltip() : (this._fireCreatedEvent(), this.disable(), void(this.options.repeatMode && this.enable())) }, _shapeIsValid: function() { return !0 }, _onZoomEnd: function() { this._updateGuide() }, _onMouseMove: function(t) { var e = t.layerPoint, - i = t.latlng; - this._currentLatLng = i, this._updateTooltip(i), this._updateGuide(e), this._mouseMarker.setLatLng(i), L.DomEvent.preventDefault(t.originalEvent) }, _vertexChanged: function(t, e) { this._updateFinishHandler(), this._updateRunningMeasure(t, e), this._clearGuides(), this._updateTooltip() }, _onMouseDown: function(t) { var e = t.originalEvent; - this._mouseDownOrigin = L.point(e.clientX, e.clientY) }, _onMouseUp: function(e) { if (this._mouseDownOrigin) { var i = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(this._mouseDownOrigin); - Math.abs(i) < 9 * (t.devicePixelRatio || 1) && this.addVertex(e.latlng) } - this._mouseDownOrigin = null }, _updateFinishHandler: function() { var t = this._markers.length; - t > 1 && this._markers[t - 1].on("click", this._finishShape, this), t > 2 && this._markers[t - 2].off("click", this._finishShape, this) }, _createMarker: function(t) { var e = new L.Marker(t, { icon: this.options.icon, zIndexOffset: 2 * this.options.zIndexOffset }); return this._markerGroup.addLayer(e), e }, _updateGuide: function(t) { var e = this._markers.length; - e > 0 && (t = t || this._map.latLngToLayerPoint(this._currentLatLng), this._clearGuides(), this._drawGuide(this._map.latLngToLayerPoint(this._markers[e - 1].getLatLng()), t)) }, _updateTooltip: function(t) { var e = this._getTooltipText(); - t && this._tooltip.updatePosition(t), this._errorShown || this._tooltip.updateContent(e) }, _drawGuide: function(t, e) { var i, o, a, s = Math.floor(Math.sqrt(Math.pow(e.x - t.x, 2) + Math.pow(e.y - t.y, 2))), - r = this.options.guidelineDistance, - n = this.options.maxGuideLineLength, - l = s > n ? s - n : r; for (this._guidesContainer || (this._guidesContainer = L.DomUtil.create("div", "leaflet-draw-guides", this._overlayPane)); s > l; l += this.options.guidelineDistance) i = l / s, o = { x: Math.floor(t.x * (1 - i) + i * e.x), y: Math.floor(t.y * (1 - i) + i * e.y) }, a = L.DomUtil.create("div", "leaflet-draw-guide-dash", this._guidesContainer), a.style.backgroundColor = this._errorShown ? this.options.drawError.color : this.options.shapeOptions.color, L.DomUtil.setPosition(a, o) }, _updateGuideColor: function(t) { if (this._guidesContainer) - for (var e = 0, i = this._guidesContainer.childNodes.length; i > e; e++) this._guidesContainer.childNodes[e].style.backgroundColor = t }, _clearGuides: function() { if (this._guidesContainer) - for (; this._guidesContainer.firstChild;) this._guidesContainer.removeChild(this._guidesContainer.firstChild) }, _getTooltipText: function() { var t, e, i = this.options.showLength; return 0 === this._markers.length ? t = { text: L.drawLocal.draw.handlers.polyline.tooltip.start } : (e = i ? this._getMeasurementString() : "", t = 1 === this._markers.length ? { text: L.drawLocal.draw.handlers.polyline.tooltip.cont, subtext: e } : { text: L.drawLocal.draw.handlers.polyline.tooltip.end, subtext: e }), t }, _updateRunningMeasure: function(t, e) { var i, o, a = this._markers.length; - 1 === this._markers.length ? this._measurementRunningTotal = 0 : (i = a - (e ? 2 : 1), o = t.distanceTo(this._markers[i].getLatLng()), this._measurementRunningTotal += o * (e ? 1 : -1)) }, _getMeasurementString: function() { var t, e = this._currentLatLng, - i = this._markers[this._markers.length - 1].getLatLng(); return t = this._measurementRunningTotal + e.distanceTo(i), L.GeometryUtil.readableDistance(t, this.options.metric) }, _showErrorTooltip: function() { this._errorShown = !0, this._tooltip.showAsError().updateContent({ text: this.options.drawError.message }), this._updateGuideColor(this.options.drawError.color), this._poly.setStyle({ color: this.options.drawError.color }), this._clearHideErrorTimeout(), this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout) }, _hideErrorTooltip: function() { this._errorShown = !1, this._clearHideErrorTimeout(), this._tooltip.removeError().updateContent(this._getTooltipText()), this._updateGuideColor(this.options.shapeOptions.color), this._poly.setStyle({ color: this.options.shapeOptions.color }) }, _clearHideErrorTimeout: function() { this._hideErrorTimeout && (clearTimeout(this._hideErrorTimeout), this._hideErrorTimeout = null) }, _cleanUpShape: function() { this._markers.length > 1 && this._markers[this._markers.length - 1].off("click", this._finishShape, this) }, _fireCreatedEvent: function() { var t = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions); - L.Draw.Feature.prototype._fireCreatedEvent.call(this, t) } }), L.Draw.Polygon = L.Draw.Polyline.extend({ statics: { TYPE: "polygon" }, Poly: L.Polygon, options: { showArea: !1, shapeOptions: { stroke: !0, color: "#f06eaa", weight: 4, opacity: .5, fill: !0, fillColor: null, fillOpacity: .2, clickable: !0 } }, initialize: function(t, e) { L.Draw.Polyline.prototype.initialize.call(this, t, e), this.type = L.Draw.Polygon.TYPE }, _updateFinishHandler: function() { var t = this._markers.length; - 1 === t && this._markers[0].on("click", this._finishShape, this), t > 2 && (this._markers[t - 1].on("dblclick", this._finishShape, this), t > 3 && this._markers[t - 2].off("dblclick", this._finishShape, this)) }, _getTooltipText: function() { var t, e; return 0 === this._markers.length ? t = L.drawLocal.draw.handlers.polygon.tooltip.start : this._markers.length < 3 ? t = L.drawLocal.draw.handlers.polygon.tooltip.cont : (t = L.drawLocal.draw.handlers.polygon.tooltip.end, e = this._getMeasurementString()), { text: t, subtext: e } }, _getMeasurementString: function() { var t = this._area; return t ? L.GeometryUtil.readableArea(t, this.options.metric) : null }, _shapeIsValid: function() { return this._markers.length >= 3 }, _vertexAdded: function() { if (!this.options.allowIntersection && this.options.showArea) { var t = this._poly.getLatLngs(); - this._area = L.GeometryUtil.geodesicArea(t) } }, _cleanUpShape: function() { var t = this._markers.length; - t > 0 && (this._markers[0].off("click", this._finishShape, this), t > 2 && this._markers[t - 1].off("dblclick", this._finishShape, this)) } }), L.SimpleShape = {}, L.Draw.SimpleShape = L.Draw.Feature.extend({ options: { repeatMode: !1 }, initialize: function(t, e) { this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end, L.Draw.Feature.prototype.initialize.call(this, t, e) }, addHooks: function() { L.Draw.Feature.prototype.addHooks.call(this), this._map && (this._mapDraggable = this._map.dragging.enabled(), this._mapDraggable && this._map.dragging.disable(), this._container.style.cursor = "crosshair", this._tooltip.updateContent({ text: this._initialLabelText }), this._map.on("mousedown", this._onMouseDown, this).on("mousemove", this._onMouseMove, this)) }, removeHooks: function() { L.Draw.Feature.prototype.removeHooks.call(this), this._map && (this._mapDraggable && this._map.dragging.enable(), this._container.style.cursor = "", this._map.off("mousedown", this._onMouseDown, this).off("mousemove", this._onMouseMove, this), L.DomEvent.off(e, "mouseup", this._onMouseUp, this), this._shape && (this._map.removeLayer(this._shape), delete this._shape)), this._isDrawing = !1 }, _onMouseDown: function(t) { this._isDrawing = !0, this._startLatLng = t.latlng, L.DomEvent.on(e, "mouseup", this._onMouseUp, this).preventDefault(t.originalEvent) }, _onMouseMove: function(t) { var e = t.latlng; - this._tooltip.updatePosition(e), this._isDrawing && (this._tooltip.updateContent({ text: this._endLabelText }), this._drawShape(e)) }, _onMouseUp: function() { this._shape && this._fireCreatedEvent(), this.disable(), this.options.repeatMode && this.enable() } }), L.Draw.Rectangle = L.Draw.SimpleShape.extend({ statics: { TYPE: "rectangle" }, options: { shapeOptions: { stroke: !0, color: "#f06eaa", weight: 4, opacity: .5, fill: !0, fillColor: null, fillOpacity: .2, clickable: !0 } }, initialize: function(t, e) { this.type = L.Draw.Rectangle.TYPE, this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start, L.Draw.SimpleShape.prototype.initialize.call(this, t, e) }, _drawShape: function(t) { this._shape ? this._shape.setBounds(new L.LatLngBounds(this._startLatLng, t)) : (this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, t), this.options.shapeOptions), this._map.addLayer(this._shape)) }, _fireCreatedEvent: function() { var t = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions); - L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, t) } }), L.Draw.Circle = L.Draw.SimpleShape.extend({ statics: { TYPE: "circle" }, options: { shapeOptions: { stroke: !0, color: "#f06eaa", weight: 4, opacity: .5, fill: !0, fillColor: null, fillOpacity: .2, clickable: !0 }, showRadius: !0, metric: !0 }, initialize: function(t, e) { this.type = L.Draw.Circle.TYPE, this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start, L.Draw.SimpleShape.prototype.initialize.call(this, t, e) }, _drawShape: function(t) { this._shape ? this._shape.setRadius(this._startLatLng.distanceTo(t)) : (this._shape = new L.Circle(this._startLatLng, this._startLatLng.distanceTo(t), this.options.shapeOptions), this._map.addLayer(this._shape)) }, _fireCreatedEvent: function() { var t = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions); - L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, t) }, _onMouseMove: function(t) { var e, i = t.latlng, - o = this.options.showRadius, - a = this.options.metric; - this._tooltip.updatePosition(i), this._isDrawing && (this._drawShape(i), e = this._shape.getRadius().toFixed(1), this._tooltip.updateContent({ text: this._endLabelText, subtext: o ? "Radius: " + L.GeometryUtil.readableDistance(e, a) : "" })) } }), L.Draw.Marker = L.Draw.Feature.extend({ statics: { TYPE: "marker" }, options: { icon: new L.Icon.Default, repeatMode: !1, zIndexOffset: 2e3 }, initialize: function(t, e) { this.type = L.Draw.Marker.TYPE, L.Draw.Feature.prototype.initialize.call(this, t, e) }, addHooks: function() { L.Draw.Feature.prototype.addHooks.call(this), this._map && (this._tooltip.updateContent({ text: L.drawLocal.draw.handlers.marker.tooltip.start }), this._mouseMarker || (this._mouseMarker = L.marker(this._map.getCenter(), { icon: L.divIcon({ className: "leaflet-mouse-marker", iconAnchor: [20, 20], iconSize: [40, 40] }), opacity: 0, zIndexOffset: this.options.zIndexOffset })), this._mouseMarker.on("click", this._onClick, this).addTo(this._map), this._map.on("mousemove", this._onMouseMove, this)) }, removeHooks: function() { L.Draw.Feature.prototype.removeHooks.call(this), this._map && (this._marker && (this._marker.off("click", this._onClick, this), this._map.off("click", this._onClick, this).removeLayer(this._marker), delete this._marker), this._mouseMarker.off("click", this._onClick, this), this._map.removeLayer(this._mouseMarker), delete this._mouseMarker, this._map.off("mousemove", this._onMouseMove, this)) }, _onMouseMove: function(t) { var e = t.latlng; - this._tooltip.updatePosition(e), this._mouseMarker.setLatLng(e), this._marker ? (e = this._mouseMarker.getLatLng(), this._marker.setLatLng(e)) : (this._marker = new L.Marker(e, { icon: this.options.icon, zIndexOffset: this.options.zIndexOffset }), this._marker.on("click", this._onClick, this), this._map.on("click", this._onClick, this).addLayer(this._marker)) }, _onClick: function() { this._fireCreatedEvent(), this.disable(), this.options.repeatMode && this.enable() }, _fireCreatedEvent: function() { var t = new L.Marker(this._marker.getLatLng(), { icon: this.options.icon }); - L.Draw.Feature.prototype._fireCreatedEvent.call(this, t) } }), L.Edit = L.Edit || {}, L.Edit.Poly = L.Handler.extend({ options: { icon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: "leaflet-div-icon leaflet-editing-icon" }) }, initialize: function(t, e) { this._poly = t, L.setOptions(this, e) }, addHooks: function() { this._poly._map && (this._markerGroup || this._initMarkers(), this._poly._map.addLayer(this._markerGroup)) }, removeHooks: function() { this._poly._map && (this._poly._map.removeLayer(this._markerGroup), delete this._markerGroup, delete this._markers) }, updateMarkers: function() { this._markerGroup.clearLayers(), this._initMarkers() }, _initMarkers: function() { this._markerGroup || (this._markerGroup = new L.LayerGroup), this._markers = []; var t, e, i, o, a = this._poly._latlngs; for (t = 0, i = a.length; i > t; t++) o = this._createMarker(a[t], t), o.on("click", this._onMarkerClick, this), this._markers.push(o); var s, r; for (t = 0, e = i - 1; i > t; e = t++)(0 !== t || L.Polygon && this._poly instanceof L.Polygon) && (s = this._markers[e], r = this._markers[t], this._createMiddleMarker(s, r), this._updatePrevNext(s, r)) }, _createMarker: function(t, e) { var i = new L.Marker(t, { draggable: !0, icon: this.options.icon }); return i._origLatLng = t, i._index = e, i.on("drag", this._onMarkerDrag, this), i.on("dragend", this._fireEdit, this), this._markerGroup.addLayer(i), i }, _removeMarker: function(t) { var e = t._index; - this._markerGroup.removeLayer(t), this._markers.splice(e, 1), this._poly.spliceLatLngs(e, 1), this._updateIndexes(e, -1), t.off("drag", this._onMarkerDrag, this).off("dragend", this._fireEdit, this).off("click", this._onMarkerClick, this) }, _fireEdit: function() { this._poly.edited = !0, this._poly.fire("edit") }, _onMarkerDrag: function(t) { var e = t.target; - L.extend(e._origLatLng, e._latlng), e._middleLeft && e._middleLeft.setLatLng(this._getMiddleLatLng(e._prev, e)), e._middleRight && e._middleRight.setLatLng(this._getMiddleLatLng(e, e._next)), this._poly.redraw() }, _onMarkerClick: function(t) { var e = L.Polygon && this._poly instanceof L.Polygon ? 4 : 3, - i = t.target; - this._poly._latlngs.length < e || (this._removeMarker(i), this._updatePrevNext(i._prev, i._next), i._middleLeft && this._markerGroup.removeLayer(i._middleLeft), i._middleRight && this._markerGroup.removeLayer(i._middleRight), i._prev && i._next ? this._createMiddleMarker(i._prev, i._next) : i._prev ? i._next || (i._prev._middleRight = null) : i._next._middleLeft = null, this._fireEdit()) }, _updateIndexes: function(t, e) { this._markerGroup.eachLayer(function(i) { i._index > t && (i._index += e) }) }, _createMiddleMarker: function(t, e) { var i, o, a, s = this._getMiddleLatLng(t, e), - r = this._createMarker(s); - r.setOpacity(.6), t._middleRight = e._middleLeft = r, o = function() { var o = e._index; - r._index = o, r.off("click", i, this).on("click", this._onMarkerClick, this), s.lat = r.getLatLng().lat, s.lng = r.getLatLng().lng, this._poly.spliceLatLngs(o, 0, s), this._markers.splice(o, 0, r), r.setOpacity(1), this._updateIndexes(o, 1), e._index++, this._updatePrevNext(t, r), this._updatePrevNext(r, e), this._poly.fire("editstart") }, a = function() { r.off("dragstart", o, this), r.off("dragend", a, this), this._createMiddleMarker(t, r), this._createMiddleMarker(r, e) }, i = function() { o.call(this), a.call(this), this._fireEdit() }, r.on("click", i, this).on("dragstart", o, this).on("dragend", a, this), this._markerGroup.addLayer(r) }, _updatePrevNext: function(t, e) { t && (t._next = e), e && (e._prev = t) }, _getMiddleLatLng: function(t, e) { var i = this._poly._map, - o = i.project(t.getLatLng()), - a = i.project(e.getLatLng()); return i.unproject(o._add(a)._divideBy(2)) } }), L.Polyline.addInitHook(function() { this.editing || (L.Edit.Poly && (this.editing = new L.Edit.Poly(this), this.options.editable && this.editing.enable()), this.on("add", function() { this.editing && this.editing.enabled() && this.editing.addHooks() }), this.on("remove", function() { this.editing && this.editing.enabled() && this.editing.removeHooks() })) }), L.Edit = L.Edit || {}, L.Edit.SimpleShape = L.Handler.extend({ options: { moveIcon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: "leaflet-div-icon leaflet-editing-icon leaflet-edit-move" }), resizeIcon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: "leaflet-div-icon leaflet-editing-icon leaflet-edit-resize" }) }, initialize: function(t, e) { this._shape = t, L.Util.setOptions(this, e) }, addHooks: function() { this._shape._map && (this._map = this._shape._map, this._markerGroup || this._initMarkers(), this._map.addLayer(this._markerGroup)) }, removeHooks: function() { if (this._shape._map) { this._unbindMarker(this._moveMarker); for (var t = 0, e = this._resizeMarkers.length; e > t; t++) this._unbindMarker(this._resizeMarkers[t]); - this._resizeMarkers = null, this._map.removeLayer(this._markerGroup), delete this._markerGroup } - this._map = null }, updateMarkers: function() { this._markerGroup.clearLayers(), this._initMarkers() }, _initMarkers: function() { this._markerGroup || (this._markerGroup = new L.LayerGroup), this._createMoveMarker(), this._createResizeMarker() }, _createMoveMarker: function() {}, _createResizeMarker: function() {}, _createMarker: function(t, e) { var i = new L.Marker(t, { draggable: !0, icon: e, zIndexOffset: 10 }); return this._bindMarker(i), this._markerGroup.addLayer(i), i }, _bindMarker: function(t) { t.on("dragstart", this._onMarkerDragStart, this).on("drag", this._onMarkerDrag, this).on("dragend", this._onMarkerDragEnd, this) }, _unbindMarker: function(t) { t.off("dragstart", this._onMarkerDragStart, this).off("drag", this._onMarkerDrag, this).off("dragend", this._onMarkerDragEnd, this) }, _onMarkerDragStart: function(t) { var e = t.target; - e.setOpacity(0), this._shape.fire("editstart") }, _fireEdit: function() { this._shape.edited = !0, this._shape.fire("edit") }, _onMarkerDrag: function(t) { var e = t.target, - i = e.getLatLng(); - e === this._moveMarker ? this._move(i) : this._resize(i), this._shape.redraw() }, _onMarkerDragEnd: function(t) { var e = t.target; - e.setOpacity(1), this._fireEdit() }, _move: function() {}, _resize: function() {} }), L.Edit = L.Edit || {}, L.Edit.Rectangle = L.Edit.SimpleShape.extend({ _createMoveMarker: function() { var t = this._shape.getBounds(), - e = t.getCenter(); - this._moveMarker = this._createMarker(e, this.options.moveIcon) }, _createResizeMarker: function() { var t = this._getCorners(); - this._resizeMarkers = []; for (var e = 0, i = t.length; i > e; e++) this._resizeMarkers.push(this._createMarker(t[e], this.options.resizeIcon)), this._resizeMarkers[e]._cornerIndex = e }, _onMarkerDragStart: function(t) { L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, t); var e = this._getCorners(), - i = t.target, - o = i._cornerIndex; - this._oppositeCorner = e[(o + 2) % 4], this._toggleCornerMarkers(0, o) }, _onMarkerDragEnd: function(t) { var e, i, o = t.target; - o === this._moveMarker && (e = this._shape.getBounds(), i = e.getCenter(), o.setLatLng(i)), this._toggleCornerMarkers(1), this._repositionCornerMarkers(), L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, t) }, _move: function(t) { for (var e, i = this._shape.getLatLngs(), o = this._shape.getBounds(), a = o.getCenter(), s = [], r = 0, n = i.length; n > r; r++) e = [i[r].lat - a.lat, i[r].lng - a.lng], s.push([t.lat + e[0], t.lng + e[1]]); - this._shape.setLatLngs(s), this._repositionCornerMarkers() }, _resize: function(t) { var e; - this._shape.setBounds(L.latLngBounds(t, this._oppositeCorner)), e = this._shape.getBounds(), this._moveMarker.setLatLng(e.getCenter()) }, _getCorners: function() { var t = this._shape.getBounds(), - e = t.getNorthWest(), - i = t.getNorthEast(), - o = t.getSouthEast(), - a = t.getSouthWest(); return [e, i, o, a] }, _toggleCornerMarkers: function(t) { for (var e = 0, i = this._resizeMarkers.length; i > e; e++) this._resizeMarkers[e].setOpacity(t) }, _repositionCornerMarkers: function() { for (var t = this._getCorners(), e = 0, i = this._resizeMarkers.length; i > e; e++) this._resizeMarkers[e].setLatLng(t[e]) } }), L.Rectangle.addInitHook(function() { L.Edit.Rectangle && (this.editing = new L.Edit.Rectangle(this), this.options.editable && this.editing.enable()) }), L.Edit = L.Edit || {}, L.Edit.Circle = L.Edit.SimpleShape.extend({ _createMoveMarker: function() { var t = this._shape.getLatLng(); - this._moveMarker = this._createMarker(t, this.options.moveIcon) }, _createResizeMarker: function() { var t = this._shape.getLatLng(), - e = this._getResizeMarkerPoint(t); - this._resizeMarkers = [], this._resizeMarkers.push(this._createMarker(e, this.options.resizeIcon)) }, _getResizeMarkerPoint: function(t) { var e = this._shape._radius * Math.cos(Math.PI / 4), - i = this._map.project(t); return this._map.unproject([i.x + e, i.y - e]) }, _move: function(t) { var e = this._getResizeMarkerPoint(t); - this._resizeMarkers[0].setLatLng(e), this._shape.setLatLng(t) }, _resize: function(t) { var e = this._moveMarker.getLatLng(), - i = e.distanceTo(t); - this._shape.setRadius(i) } }), L.Circle.addInitHook(function() { L.Edit.Circle && (this.editing = new L.Edit.Circle(this), this.options.editable && this.editing.enable()), this.on("add", function() { this.editing && this.editing.enabled() && this.editing.addHooks() }), this.on("remove", function() { this.editing && this.editing.enabled() && this.editing.removeHooks() }) }), L.LatLngUtil = { cloneLatLngs: function(t) { for (var e = [], i = 0, o = t.length; o > i; i++) e.push(this.cloneLatLng(t[i])); return e }, cloneLatLng: function(t) { return L.latLng(t.lat, t.lng) } }, L.GeometryUtil = L.extend(L.GeometryUtil || {}, { geodesicArea: function(t) { var e, i, o = t.length, - a = 0, - s = L.LatLng.DEG_TO_RAD; if (o > 2) { for (var r = 0; o > r; r++) e = t[r], i = t[(r + 1) % o], a += (i.lng - e.lng) * s * (2 + Math.sin(e.lat * s) + Math.sin(i.lat * s)); - a = 6378137 * a * 6378137 / 2 } return Math.abs(a) }, readableArea: function(t, e) { var i; return e ? i = t >= 1e4 ? (1e-4 * t).toFixed(2) + " ha" : t.toFixed(2) + " m²" : (t *= .836127, i = t >= 3097600 ? (t / 3097600).toFixed(2) + " mi²" : t >= 4840 ? (t / 4840).toFixed(2) + " acres" : Math.ceil(t) + " yd²"), i }, readableDistance: function(t, e) { var i; return e ? i = t > 1e3 ? (t / 1e3).toFixed(2) + " km" : Math.ceil(t) + " m" : (t *= 1.09361, i = t > 1760 ? (t / 1760).toFixed(2) + " miles" : Math.ceil(t) + " yd"), i } }), L.Util.extend(L.LineUtil, { segmentsIntersect: function(t, e, i, o) { return this._checkCounterclockwise(t, i, o) !== this._checkCounterclockwise(e, i, o) && this._checkCounterclockwise(t, e, i) !== this._checkCounterclockwise(t, e, o) }, _checkCounterclockwise: function(t, e, i) { return (i.y - t.y) * (e.x - t.x) > (e.y - t.y) * (i.x - t.x) } }), L.Polyline.include({ intersects: function() { var t, e, i, o = this._originalPoints, - a = o ? o.length : 0; if (this._tooFewPointsForIntersection()) return !1; for (t = a - 1; t >= 3; t--) - if (e = o[t - 1], i = o[t], this._lineSegmentsIntersectsRange(e, i, t - 2)) return !0; - return !1 }, newLatLngIntersects: function(t, e) { return this._map ? this.newPointIntersects(this._map.latLngToLayerPoint(t), e) : !1 }, newPointIntersects: function(t, e) { var i = this._originalPoints, - o = i ? i.length : 0, - a = i ? i[o - 1] : null, - s = o - 2; return this._tooFewPointsForIntersection(1) ? !1 : this._lineSegmentsIntersectsRange(a, t, s, e ? 1 : 0) }, _tooFewPointsForIntersection: function(t) { var e = this._originalPoints, - i = e ? e.length : 0; return i += t || 0, !this._originalPoints || 3 >= i }, _lineSegmentsIntersectsRange: function(t, e, i, o) { var a, s, r = this._originalPoints; - o = o || 0; for (var n = i; n > o; n--) - if (a = r[n - 1], s = r[n], L.LineUtil.segmentsIntersect(t, e, a, s)) return !0; - return !1 } }), L.Polygon.include({ intersects: function() { var t, e, i, o, a, s = this._originalPoints; return this._tooFewPointsForIntersection() ? !1 : (t = L.Polyline.prototype.intersects.call(this)) ? !0 : (e = s.length, i = s[0], o = s[e - 1], a = e - 2, this._lineSegmentsIntersectsRange(o, i, a, 1)) } }), L.Control.Draw = L.Control.extend({ options: { position: "topleft", draw: {}, edit: !1 }, initialize: function(t) { if (L.version < "0.7") throw new Error("Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/"); - L.Control.prototype.initialize.call(this, t); var e, i; - this._toolbars = {}, L.DrawToolbar && this.options.draw && (i = new L.DrawToolbar(this.options.draw), e = L.stamp(i), this._toolbars[e] = i, this._toolbars[e].on("enable", this._toolbarEnabled, this)), L.EditToolbar && this.options.edit && (i = new L.EditToolbar(this.options.edit), e = L.stamp(i), this._toolbars[e] = i, this._toolbars[e].on("enable", this._toolbarEnabled, this)) }, onAdd: function(t) { var e, i = L.DomUtil.create("div", "leaflet-draw"), - o = !1, - a = "leaflet-draw-toolbar-top"; for (var s in this._toolbars) this._toolbars.hasOwnProperty(s) && (e = this._toolbars[s].addToolbar(t), e && (o || (L.DomUtil.hasClass(e, a) || L.DomUtil.addClass(e.childNodes[0], a), o = !0), i.appendChild(e))); return i }, onRemove: function() { for (var t in this._toolbars) this._toolbars.hasOwnProperty(t) && this._toolbars[t].removeToolbar() }, setDrawingOptions: function(t) { for (var e in this._toolbars) this._toolbars[e] instanceof L.DrawToolbar && this._toolbars[e].setOptions(t) }, _toolbarEnabled: function(t) { var e = "" + L.stamp(t.target); for (var i in this._toolbars) this._toolbars.hasOwnProperty(i) && i !== e && this._toolbars[i].disable() } }), L.Map.mergeOptions({ drawControlTooltips: !0, drawControl: !1 }), L.Map.addInitHook(function() { this.options.drawControl && (this.drawControl = new L.Control.Draw, this.addControl(this.drawControl)) }), L.Toolbar = L.Class.extend({ - includes: [L.Mixin.Events], - initialize: function(t) { L.setOptions(this, t), this._modes = {}, this._actionButtons = [], this._activeMode = null }, - enabled: function() { return null !== this._activeMode }, - disable: function() { this.enabled() && this._activeMode.handler.disable() }, - addToolbar: function(t) { var e, i = L.DomUtil.create("div", "leaflet-draw-section"), - o = 0, - a = this._toolbarClass || "", - s = this.getModeHandlers(t); for (this._toolbarContainer = L.DomUtil.create("div", "leaflet-draw-toolbar leaflet-bar"), this._map = t, e = 0; e < s.length; e++) s[e].enabled && this._initModeHandler(s[e].handler, this._toolbarContainer, o++, a, s[e].title); return o ? (this._lastButtonIndex = --o, this._actionsContainer = L.DomUtil.create("ul", "leaflet-draw-actions"), i.appendChild(this._toolbarContainer), i.appendChild(this._actionsContainer), i) : void 0 }, - removeToolbar: function() { for (var t in this._modes) this._modes.hasOwnProperty(t) && (this._disposeButton(this._modes[t].button, this._modes[t].handler.enable, this._modes[t].handler), this._modes[t].handler.disable(), this._modes[t].handler.off("enabled", this._handlerActivated, this).off("disabled", this._handlerDeactivated, this)); - this._modes = {}; for (var e = 0, i = this._actionButtons.length; i > e; e++) this._disposeButton(this._actionButtons[e].button, this._actionButtons[e].callback, this); - this._actionButtons = [], this._actionsContainer = null }, - _initModeHandler: function(t, e, i, o, a) { var s = t.type; - this._modes[s] = {}, this._modes[s].handler = t, this._modes[s].button = this._createButton({ title: a, className: o + "-" + s, container: e, callback: this._modes[s].handler.enable, context: this._modes[s].handler }), this._modes[s].buttonIndex = i, this._modes[s].handler.on("enabled", this._handlerActivated, this).on("disabled", this._handlerDeactivated, this) }, - _createButton: function(t) { var e = L.DomUtil.create("a", t.className || "", t.container); return e.href = "#", t.text && (e.innerHTML = t.text), t.title && (e.title = t.title), L.DomEvent.on(e, "click", L.DomEvent.stopPropagation).on(e, "mousedown", L.DomEvent.stopPropagation).on(e, "dblclick", L.DomEvent.stopPropagation).on(e, "click", L.DomEvent.preventDefault).on(e, "click", t.callback, t.context), e }, - _disposeButton: function(t, e) { L.DomEvent.off(t, "click", L.DomEvent.stopPropagation).off(t, "mousedown", L.DomEvent.stopPropagation).off(t, "dblclick", L.DomEvent.stopPropagation).off(t, "click", L.DomEvent.preventDefault).off(t, "click", e) }, - _handlerActivated: function(t) { this.disable(), this._activeMode = this._modes[t.handler], L.DomUtil.addClass(this._activeMode.button, "leaflet-draw-toolbar-button-enabled"), this._showActionsToolbar(), this.fire("enable") }, - _handlerDeactivated: function() { this._hideActionsToolbar(), L.DomUtil.removeClass(this._activeMode.button, "leaflet-draw-toolbar-button-enabled"), this._activeMode = null, this.fire("disable") }, - _createActions: function(t) { var e, i, o, a, s = this._actionsContainer, - r = this.getActions(t), - n = r.length; for (i = 0, o = this._actionButtons.length; o > i; i++) this._disposeButton(this._actionButtons[i].button, this._actionButtons[i].callback); for (this._actionButtons = []; s.firstChild;) s.removeChild(s.firstChild); for (var l = 0; n > l; l++) "enabled" in r[l] && !r[l].enabled || (e = L.DomUtil.create("li", "", s), a = this._createButton({ title: r[l].title, text: r[l].text, container: e, callback: r[l].callback, context: r[l].context }), this._actionButtons.push({ button: a, callback: r[l].callback })) }, - _showActionsToolbar: function() { - var t = this._activeMode.buttonIndex, - e = this._lastButtonIndex, - i = this._activeMode.button.offsetTop - 1; - this._createActions(this._activeMode.handler), this._actionsContainer.style.top = i + "px", 0 === t && (L.DomUtil.addClass(this._toolbarContainer, "leaflet-draw-toolbar-notop"), L.DomUtil.addClass(this._actionsContainer, "leaflet-draw-actions-top")), t === e && (L.DomUtil.addClass(this._toolbarContainer, "leaflet-draw-toolbar-nobottom"), L.DomUtil.addClass(this._actionsContainer, "leaflet-draw-actions-bottom")), this._actionsContainer.style.display = "block" - }, - _hideActionsToolbar: function() { this._actionsContainer.style.display = "none", L.DomUtil.removeClass(this._toolbarContainer, "leaflet-draw-toolbar-notop"), L.DomUtil.removeClass(this._toolbarContainer, "leaflet-draw-toolbar-nobottom"), L.DomUtil.removeClass(this._actionsContainer, "leaflet-draw-actions-top"), L.DomUtil.removeClass(this._actionsContainer, "leaflet-draw-actions-bottom") } - }), L.Tooltip = L.Class.extend({ initialize: function(t) { this._map = t, this._popupPane = t._panes.popupPane, this._container = t.options.drawControlTooltips ? L.DomUtil.create("div", "leaflet-draw-tooltip", this._popupPane) : null, this._singleLineLabel = !1 }, dispose: function() { this._container && (this._popupPane.removeChild(this._container), this._container = null) }, updateContent: function(t) { return this._container ? (t.subtext = t.subtext || "", 0 !== t.subtext.length || this._singleLineLabel ? t.subtext.length > 0 && this._singleLineLabel && (L.DomUtil.removeClass(this._container, "leaflet-draw-tooltip-single"), this._singleLineLabel = !1) : (L.DomUtil.addClass(this._container, "leaflet-draw-tooltip-single"), this._singleLineLabel = !0), this._container.innerHTML = (t.subtext.length > 0 ? '' + t.subtext + "
" : "") + "" + t.text + "", this) : this }, updatePosition: function(t) { var e = this._map.latLngToLayerPoint(t), - i = this._container; return this._container && (i.style.visibility = "inherit", L.DomUtil.setPosition(i, e)), this }, showAsError: function() { return this._container && L.DomUtil.addClass(this._container, "leaflet-error-draw-tooltip"), this }, removeError: function() { return this._container && L.DomUtil.removeClass(this._container, "leaflet-error-draw-tooltip"), this } }), L.DrawToolbar = L.Toolbar.extend({ options: { polyline: {}, polygon: {}, rectangle: {}, circle: {}, marker: {} }, initialize: function(t) { for (var e in this.options) this.options.hasOwnProperty(e) && t[e] && (t[e] = L.extend({}, this.options[e], t[e])); - this._toolbarClass = "leaflet-draw-draw", L.Toolbar.prototype.initialize.call(this, t) }, getModeHandlers: function(t) { return [{ enabled: this.options.polyline, handler: new L.Draw.Polyline(t, this.options.polyline), title: L.drawLocal.draw.toolbar.buttons.polyline }, { enabled: this.options.polygon, handler: new L.Draw.Polygon(t, this.options.polygon), title: L.drawLocal.draw.toolbar.buttons.polygon }, { enabled: this.options.rectangle, handler: new L.Draw.Rectangle(t, this.options.rectangle), title: L.drawLocal.draw.toolbar.buttons.rectangle }, { enabled: this.options.circle, handler: new L.Draw.Circle(t, this.options.circle), title: L.drawLocal.draw.toolbar.buttons.circle }, { enabled: this.options.marker, handler: new L.Draw.Marker(t, this.options.marker), title: L.drawLocal.draw.toolbar.buttons.marker }] }, getActions: function(t) { return [{ enabled: t.deleteLastVertex, title: L.drawLocal.draw.toolbar.undo.title, text: L.drawLocal.draw.toolbar.undo.text, callback: t.deleteLastVertex, context: t }, { title: L.drawLocal.draw.toolbar.actions.title, text: L.drawLocal.draw.toolbar.actions.text, callback: this.disable, context: this }] }, setOptions: function(t) { L.setOptions(this, t); for (var e in this._modes) this._modes.hasOwnProperty(e) && t.hasOwnProperty(e) && this._modes[e].handler.setOptions(t[e]) } }), L.EditToolbar = L.Toolbar.extend({ options: { edit: { selectedPathOptions: { color: "#fe57a1", opacity: .6, dashArray: "10, 10", fill: !0, fillColor: "#fe57a1", fillOpacity: .1 } }, remove: {}, featureGroup: null }, initialize: function(t) { t.edit && ("undefined" == typeof t.edit.selectedPathOptions && (t.edit.selectedPathOptions = this.options.edit.selectedPathOptions), t.edit = L.extend({}, this.options.edit, t.edit)), t.remove && (t.remove = L.extend({}, this.options.remove, t.remove)), this._toolbarClass = "leaflet-draw-edit", L.Toolbar.prototype.initialize.call(this, t), this._selectedFeatureCount = 0 }, getModeHandlers: function(t) { var e = this.options.featureGroup; return [{ enabled: this.options.edit, handler: new L.EditToolbar.Edit(t, { featureGroup: e, selectedPathOptions: this.options.edit.selectedPathOptions }), title: L.drawLocal.edit.toolbar.buttons.edit }, { enabled: this.options.remove, handler: new L.EditToolbar.Delete(t, { featureGroup: e }), title: L.drawLocal.edit.toolbar.buttons.remove }] }, getActions: function() { return [{ title: L.drawLocal.edit.toolbar.actions.save.title, text: L.drawLocal.edit.toolbar.actions.save.text, callback: this._save, context: this }, { title: L.drawLocal.edit.toolbar.actions.cancel.title, text: L.drawLocal.edit.toolbar.actions.cancel.text, callback: this.disable, context: this }] }, addToolbar: function(t) { var e = L.Toolbar.prototype.addToolbar.call(this, t); return this._checkDisabled(), this.options.featureGroup.on("layeradd layerremove", this._checkDisabled, this), e }, removeToolbar: function() { this.options.featureGroup.off("layeradd layerremove", this._checkDisabled, this), L.Toolbar.prototype.removeToolbar.call(this) }, disable: function() { this.enabled() && (this._activeMode.handler.revertLayers(), L.Toolbar.prototype.disable.call(this)) }, _save: function() { this._activeMode.handler.save(), this._activeMode.handler.disable() }, _checkDisabled: function() { var t, e = this.options.featureGroup, - i = 0 !== e.getLayers().length; - this.options.edit && (t = this._modes[L.EditToolbar.Edit.TYPE].button, i ? L.DomUtil.removeClass(t, "leaflet-disabled") : L.DomUtil.addClass(t, "leaflet-disabled"), t.setAttribute("title", i ? L.drawLocal.edit.toolbar.buttons.edit : L.drawLocal.edit.toolbar.buttons.editDisabled)), this.options.remove && (t = this._modes[L.EditToolbar.Delete.TYPE].button, i ? L.DomUtil.removeClass(t, "leaflet-disabled") : L.DomUtil.addClass(t, "leaflet-disabled"), t.setAttribute("title", i ? L.drawLocal.edit.toolbar.buttons.remove : L.drawLocal.edit.toolbar.buttons.removeDisabled)) } }), L.EditToolbar.Edit = L.Handler.extend({ statics: { TYPE: "edit" }, includes: L.Mixin.Events, initialize: function(t, e) { if (L.Handler.prototype.initialize.call(this, t), this._selectedPathOptions = e.selectedPathOptions, this._featureGroup = e.featureGroup, !(this._featureGroup instanceof L.FeatureGroup)) throw new Error("options.featureGroup must be a L.FeatureGroup"); - this._uneditedLayerProps = {}, this.type = L.EditToolbar.Edit.TYPE }, enable: function() {!this._enabled && this._hasAvailableLayers() && (this.fire("enabled", { handler: this.type }), this._map.fire("draw:editstart", { handler: this.type }), L.Handler.prototype.enable.call(this), this._featureGroup.on("layeradd", this._enableLayerEdit, this).on("layerremove", this._disableLayerEdit, this)) }, disable: function() { this._enabled && (this._featureGroup.off("layeradd", this._enableLayerEdit, this).off("layerremove", this._disableLayerEdit, this), L.Handler.prototype.disable.call(this), this._map.fire("draw:editstop", { handler: this.type }), this.fire("disabled", { handler: this.type })) }, addHooks: function() { var t = this._map; - t && (t.getContainer().focus(), this._featureGroup.eachLayer(this._enableLayerEdit, this), this._tooltip = new L.Tooltip(this._map), this._tooltip.updateContent({ text: L.drawLocal.edit.handlers.edit.tooltip.text, subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext }), this._map.on("mousemove", this._onMouseMove, this)) }, removeHooks: function() { this._map && (this._featureGroup.eachLayer(this._disableLayerEdit, this), this._uneditedLayerProps = {}, this._tooltip.dispose(), this._tooltip = null, this._map.off("mousemove", this._onMouseMove, this)) }, revertLayers: function() { this._featureGroup.eachLayer(function(t) { this._revertLayer(t) }, this) }, save: function() { var t = new L.LayerGroup; - this._featureGroup.eachLayer(function(e) { e.edited && (t.addLayer(e), e.edited = !1) }), this._map.fire("draw:edited", { layers: t }) }, _backupLayer: function(t) { var e = L.Util.stamp(t); - this._uneditedLayerProps[e] || (t instanceof L.Polyline || t instanceof L.Polygon || t instanceof L.Rectangle ? this._uneditedLayerProps[e] = { latlngs: L.LatLngUtil.cloneLatLngs(t.getLatLngs()) } : t instanceof L.Circle ? this._uneditedLayerProps[e] = { latlng: L.LatLngUtil.cloneLatLng(t.getLatLng()), radius: t.getRadius() } : t instanceof L.Marker && (this._uneditedLayerProps[e] = { latlng: L.LatLngUtil.cloneLatLng(t.getLatLng()) })) }, _revertLayer: function(t) { var e = L.Util.stamp(t); - t.edited = !1, this._uneditedLayerProps.hasOwnProperty(e) && (t instanceof L.Polyline || t instanceof L.Polygon || t instanceof L.Rectangle ? t.setLatLngs(this._uneditedLayerProps[e].latlngs) : t instanceof L.Circle ? (t.setLatLng(this._uneditedLayerProps[e].latlng), t.setRadius(this._uneditedLayerProps[e].radius)) : t instanceof L.Marker && t.setLatLng(this._uneditedLayerProps[e].latlng)) }, _toggleMarkerHighlight: function(t) { if (t._icon) { var e = t._icon; - e.style.display = "none", L.DomUtil.hasClass(e, "leaflet-edit-marker-selected") ? (L.DomUtil.removeClass(e, "leaflet-edit-marker-selected"), this._offsetMarker(e, -4)) : (L.DomUtil.addClass(e, "leaflet-edit-marker-selected"), this._offsetMarker(e, 4)), e.style.display = "" } }, _offsetMarker: function(t, e) { var i = parseInt(t.style.marginTop, 10) - e, - o = parseInt(t.style.marginLeft, 10) - e; - t.style.marginTop = i + "px", t.style.marginLeft = o + "px" }, _enableLayerEdit: function(t) { var e, i = t.layer || t.target || t, - o = i instanceof L.Marker; - (!o || i._icon) && (this._backupLayer(i), this._selectedPathOptions && (e = L.Util.extend({}, this._selectedPathOptions), o ? this._toggleMarkerHighlight(i) : (i.options.previousOptions = L.Util.extend({ dashArray: null }, i.options), i instanceof L.Circle || i instanceof L.Polygon || i instanceof L.Rectangle || (e.fill = !1), i.setStyle(e))), o ? (i.dragging.enable(), i.on("dragend", this._onMarkerDragEnd)) : i.editing.enable()) }, _disableLayerEdit: function(t) { var e = t.layer || t.target || t; - e.edited = !1, this._selectedPathOptions && (e instanceof L.Marker ? this._toggleMarkerHighlight(e) : (e.setStyle(e.options.previousOptions), delete e.options.previousOptions)), e instanceof L.Marker ? (e.dragging.disable(), e.off("dragend", this._onMarkerDragEnd, this)) : e.editing.disable() }, _onMarkerDragEnd: function(t) { var e = t.target; - e.edited = !0 }, _onMouseMove: function(t) { this._tooltip.updatePosition(t.latlng) }, _hasAvailableLayers: function() { return 0 !== this._featureGroup.getLayers().length } }), L.EditToolbar.Delete = L.Handler.extend({ statics: { TYPE: "remove" }, includes: L.Mixin.Events, initialize: function(t, e) { if (L.Handler.prototype.initialize.call(this, t), L.Util.setOptions(this, e), this._deletableLayers = this.options.featureGroup, !(this._deletableLayers instanceof L.FeatureGroup)) throw new Error("options.featureGroup must be a L.FeatureGroup"); - this.type = L.EditToolbar.Delete.TYPE }, enable: function() {!this._enabled && this._hasAvailableLayers() && (this.fire("enabled", { handler: this.type }), this._map.fire("draw:deletestart", { handler: this.type }), L.Handler.prototype.enable.call(this), this._deletableLayers.on("layeradd", this._enableLayerDelete, this).on("layerremove", this._disableLayerDelete, this)) }, disable: function() { this._enabled && (this._deletableLayers.off("layeradd", this._enableLayerDelete, this).off("layerremove", this._disableLayerDelete, this), L.Handler.prototype.disable.call(this), this._map.fire("draw:deletestop", { handler: this.type }), this.fire("disabled", { handler: this.type })) }, addHooks: function() { var t = this._map; - t && (t.getContainer().focus(), this._deletableLayers.eachLayer(this._enableLayerDelete, this), this._deletedLayers = new L.layerGroup, this._tooltip = new L.Tooltip(this._map), this._tooltip.updateContent({ text: L.drawLocal.edit.handlers.remove.tooltip.text }), this._map.on("mousemove", this._onMouseMove, this)) }, removeHooks: function() { this._map && (this._deletableLayers.eachLayer(this._disableLayerDelete, this), this._deletedLayers = null, this._tooltip.dispose(), this._tooltip = null, this._map.off("mousemove", this._onMouseMove, this)) }, revertLayers: function() { this._deletedLayers.eachLayer(function(t) { this._deletableLayers.addLayer(t) }, this) }, save: function() { this._map.fire("draw:deleted", { layers: this._deletedLayers }) }, _enableLayerDelete: function(t) { var e = t.layer || t.target || t; - e.on("click", this._removeLayer, this) }, _disableLayerDelete: function(t) { var e = t.layer || t.target || t; - e.off("click", this._removeLayer, this), this._deletedLayers.removeLayer(e) }, _removeLayer: function(t) { var e = t.layer || t.target || t; - this._deletableLayers.removeLayer(e), this._deletedLayers.addLayer(e) }, _onMouseMove: function(t) { this._tooltip.updatePosition(t.latlng) }, _hasAvailableLayers: function() { return 0 !== this._deletableLayers.getLayers().length } }) -}(window, document); diff --git a/erpnext/public/js/leaflet/leaflet.js b/erpnext/public/js/leaflet/leaflet.js deleted file mode 100755 index 91dd3d434c..0000000000 --- a/erpnext/public/js/leaflet/leaflet.js +++ /dev/null @@ -1,771 +0,0 @@ -/* - Leaflet 1.0.0-beta.2 (dd0faa1), a JS library for interactive maps. http://leafletjs.com - (c) 2010-2015 Vladimir Agafonkin, (c) 2010-2011 CloudMade -*/ -! function(t, e, i) { - function n() { var e = t.L; - o.noConflict = function() { return t.L = e, this }, t.L = o } - var o = { version: "1.0.0-beta.2" }; - "object" == typeof module && "object" == typeof module.exports ? module.exports = o : "function" == typeof define && define.amd && define(o), "undefined" != typeof t && n(), o.Util = { extend: function(t) { var e, i, n, o; for (i = 1, n = arguments.length; n > i; i++) { o = arguments[i]; for (e in o) t[e] = o[e] } return t }, create: Object.create || function() { - function t() {} return function(e) { return t.prototype = e, new t } }(), bind: function(t, e) { var i = Array.prototype.slice; if (t.bind) return t.bind.apply(t, i.call(arguments, 1)); var n = i.call(arguments, 2); return function() { return t.apply(e, n.length ? n.concat(i.call(arguments)) : arguments) } }, stamp: function(t) { return t._leaflet_id = t._leaflet_id || ++o.Util.lastId, t._leaflet_id }, lastId: 0, throttle: function(t, e, i) { var n, o, s, r; return r = function() { n = !1, o && (s.apply(i, o), o = !1) }, s = function() { n ? o = arguments : (t.apply(i, arguments), setTimeout(r, e), n = !0) } }, wrapNum: function(t, e, i) { var n = e[1], - o = e[0], - s = n - o; return t === n && i ? t : ((t - o) % s + s) % s + o }, falseFn: function() { return !1 }, formatNum: function(t, e) { var i = Math.pow(10, e || 5); return Math.round(t * i) / i }, trim: function(t) { return t.trim ? t.trim() : t.replace(/^\s+|\s+$/g, "") }, splitWords: function(t) { return o.Util.trim(t).split(/\s+/) }, setOptions: function(t, e) { t.hasOwnProperty("options") || (t.options = t.options ? o.Util.create(t.options) : {}); for (var i in e) t.options[i] = e[i]; return t.options }, getParamString: function(t, e, i) { var n = []; for (var o in t) n.push(encodeURIComponent(i ? o.toUpperCase() : o) + "=" + encodeURIComponent(t[o])); return (e && -1 !== e.indexOf("?") ? "&" : "?") + n.join("&") }, template: function(t, e) { return t.replace(o.Util.templateRe, function(t, n) { var o = e[n]; if (o === i) throw new Error("No value provided for variable " + t); return "function" == typeof o && (o = o(e)), o }) }, templateRe: /\{ *([\w_]+) *\}/g, isArray: Array.isArray || function(t) { return "[object Array]" === Object.prototype.toString.call(t) }, indexOf: function(t, e) { for (var i = 0; i < t.length; i++) - if (t[i] === e) return i; - return -1 }, emptyImageUrl: "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" }, - function() { - function e(e) { return t["webkit" + e] || t["moz" + e] || t["ms" + e] } - - function i(e) { var i = +new Date, - o = Math.max(0, 16 - (i - n)); return n = i + o, t.setTimeout(e, o) } var n = 0, - s = t.requestAnimationFrame || e("RequestAnimationFrame") || i, - r = t.cancelAnimationFrame || e("CancelAnimationFrame") || e("CancelRequestAnimationFrame") || function(e) { t.clearTimeout(e) }; - o.Util.requestAnimFrame = function(e, n, r) { return r && s === i ? void e.call(n) : s.call(t, o.bind(e, n)) }, o.Util.cancelAnimFrame = function(e) { e && r.call(t, e) } }(), o.extend = o.Util.extend, o.bind = o.Util.bind, o.stamp = o.Util.stamp, o.setOptions = o.Util.setOptions, o.Class = function() {}, o.Class.extend = function(t) { var e = function() { this.initialize && this.initialize.apply(this, arguments), this.callInitHooks() }, - i = e.__super__ = this.prototype, - n = o.Util.create(i); - n.constructor = e, e.prototype = n; for (var s in this) this.hasOwnProperty(s) && "prototype" !== s && (e[s] = this[s]); return t.statics && (o.extend(e, t.statics), delete t.statics), t.includes && (o.Util.extend.apply(null, [n].concat(t.includes)), delete t.includes), n.options && (t.options = o.Util.extend(o.Util.create(n.options), t.options)), o.extend(n, t), n._initHooks = [], n.callInitHooks = function() { if (!this._initHooksCalled) { i.callInitHooks && i.callInitHooks.call(this), this._initHooksCalled = !0; for (var t = 0, e = n._initHooks.length; e > t; t++) n._initHooks[t].call(this) } }, e }, o.Class.include = function(t) { o.extend(this.prototype, t) }, o.Class.mergeOptions = function(t) { o.extend(this.prototype.options, t) }, o.Class.addInitHook = function(t) { var e = Array.prototype.slice.call(arguments, 1), - i = "function" == typeof t ? t : function() { this[t].apply(this, e) }; - this.prototype._initHooks = this.prototype._initHooks || [], this.prototype._initHooks.push(i) }, o.Evented = o.Class.extend({ on: function(t, e, i) { if ("object" == typeof t) - for (var n in t) this._on(n, t[n], e); - else { t = o.Util.splitWords(t); for (var s = 0, r = t.length; r > s; s++) this._on(t[s], e, i) } return this }, off: function(t, e, i) { if (t) - if ("object" == typeof t) - for (var n in t) this._off(n, t[n], e); - else { t = o.Util.splitWords(t); for (var s = 0, r = t.length; r > s; s++) this._off(t[s], e, i) } - else delete this._events; return this }, _on: function(t, e, i) { var n = this._events = this._events || {}, - s = i && i !== this && o.stamp(i); if (s) { var r = t + "_idx", - a = t + "_len", - h = n[r] = n[r] || {}, - l = o.stamp(e) + "_" + s; - h[l] || (h[l] = { fn: e, ctx: i }, n[a] = (n[a] || 0) + 1) } else n[t] = n[t] || [], n[t].push({ fn: e }) }, _off: function(t, e, i) { var n = this._events, - s = t + "_idx", - r = t + "_len"; if (n) { if (!e) return delete n[t], delete n[s], void delete n[r]; var a, h, l, u, c, d = i && i !== this && o.stamp(i); if (d) c = o.stamp(e) + "_" + d, a = n[s], a && a[c] && (u = a[c], delete a[c], n[r]--); - else if (a = n[t]) - for (h = 0, l = a.length; l > h; h++) - if (a[h].fn === e) { u = a[h], a.splice(h, 1); break } - u && (u.fn = o.Util.falseFn) } }, fire: function(t, e, i) { if (!this.listens(t, i)) return this; var n = o.Util.extend({}, e, { type: t, target: this }), - s = this._events; if (s) { var r, a, h, l, u = s[t + "_idx"]; if (s[t]) - for (h = s[t].slice(), r = 0, a = h.length; a > r; r++) h[r].fn.call(this, n); for (l in u) u[l].fn.call(u[l].ctx, n) } return i && this._propagateEvent(n), this }, listens: function(t, e) { var i = this._events; if (i && (i[t] || i[t + "_len"])) return !0; if (e) - for (var n in this._eventParents) - if (this._eventParents[n].listens(t, e)) return !0; - return !1 }, once: function(t, e, i) { if ("object" == typeof t) { for (var n in t) this.once(n, t[n], e); return this } var s = o.bind(function() { this.off(t, e, i).off(t, s, i) }, this); return this.on(t, e, i).on(t, s, i) }, addEventParent: function(t) { return this._eventParents = this._eventParents || {}, this._eventParents[o.stamp(t)] = t, this }, removeEventParent: function(t) { return this._eventParents && delete this._eventParents[o.stamp(t)], this }, _propagateEvent: function(t) { for (var e in this._eventParents) this._eventParents[e].fire(t.type, o.extend({ layer: t.target }, t), !0) } }); - var s = o.Evented.prototype; - s.addEventListener = s.on, s.removeEventListener = s.clearAllEventListeners = s.off, s.addOneTimeEventListener = s.once, s.fireEvent = s.fire, s.hasEventListeners = s.listens, o.Mixin = { Events: s }, - function() { var i = navigator.userAgent.toLowerCase(), - n = e.documentElement, - s = "ActiveXObject" in t, - r = -1 !== i.indexOf("webkit"), - a = -1 !== i.indexOf("phantom"), - h = -1 !== i.search("android [23]"), - l = -1 !== i.indexOf("chrome"), - u = -1 !== i.indexOf("gecko") && !r && !t.opera && !s, - c = "undefined" != typeof orientation || -1 !== i.indexOf("mobile"), - d = !t.PointerEvent && t.MSPointerEvent, - _ = t.PointerEvent && navigator.pointerEnabled || d, - m = s && "transition" in n.style, - p = "WebKitCSSMatrix" in t && "m11" in new t.WebKitCSSMatrix && !h, - f = "MozPerspective" in n.style, - g = "OTransition" in n.style, - v = !t.L_NO_TOUCH && !a && (_ || "ontouchstart" in t || t.DocumentTouch && e instanceof t.DocumentTouch); - o.Browser = { ie: s, ielt9: s && !e.addEventListener, webkit: r, gecko: u, android: -1 !== i.indexOf("android"), android23: h, chrome: l, safari: !l && -1 !== i.indexOf("safari"), ie3d: m, webkit3d: p, gecko3d: f, opera12: g, any3d: !t.L_DISABLE_3D && (m || p || f) && !g && !a, mobile: c, mobileWebkit: c && r, mobileWebkit3d: c && p, mobileOpera: c && t.opera, mobileGecko: c && u, touch: !!v, msPointer: !!d, pointer: !!_, retina: (t.devicePixelRatio || t.screen.deviceXDPI / t.screen.logicalXDPI) > 1 } }(), o.Point = function(t, e, i) { this.x = i ? Math.round(t) : t, this.y = i ? Math.round(e) : e }, o.Point.prototype = { clone: function() { return new o.Point(this.x, this.y) }, add: function(t) { return this.clone()._add(o.point(t)) }, _add: function(t) { return this.x += t.x, this.y += t.y, this }, subtract: function(t) { return this.clone()._subtract(o.point(t)) }, _subtract: function(t) { return this.x -= t.x, this.y -= t.y, this }, divideBy: function(t) { return this.clone()._divideBy(t) }, _divideBy: function(t) { return this.x /= t, this.y /= t, this }, multiplyBy: function(t) { return this.clone()._multiplyBy(t) }, _multiplyBy: function(t) { return this.x *= t, this.y *= t, this }, scaleBy: function(t) { return new o.Point(this.x * t.x, this.y * t.y) }, unscaleBy: function(t) { return new o.Point(this.x / t.x, this.y / t.y) }, round: function() { return this.clone()._round() }, _round: function() { return this.x = Math.round(this.x), this.y = Math.round(this.y), this }, floor: function() { return this.clone()._floor() }, _floor: function() { return this.x = Math.floor(this.x), this.y = Math.floor(this.y), this }, ceil: function() { return this.clone()._ceil() }, _ceil: function() { return this.x = Math.ceil(this.x), this.y = Math.ceil(this.y), this }, distanceTo: function(t) { t = o.point(t); var e = t.x - this.x, - i = t.y - this.y; return Math.sqrt(e * e + i * i) }, equals: function(t) { return t = o.point(t), t.x === this.x && t.y === this.y }, contains: function(t) { return t = o.point(t), Math.abs(t.x) <= Math.abs(this.x) && Math.abs(t.y) <= Math.abs(this.y) }, toString: function() { return "Point(" + o.Util.formatNum(this.x) + ", " + o.Util.formatNum(this.y) + ")" } }, o.point = function(t, e, n) { return t instanceof o.Point ? t : o.Util.isArray(t) ? new o.Point(t[0], t[1]) : t === i || null === t ? t : new o.Point(t, e, n) }, o.Bounds = function(t, e) { if (t) - for (var i = e ? [t, e] : t, n = 0, o = i.length; o > n; n++) this.extend(i[n]) }, o.Bounds.prototype = { extend: function(t) { return t = o.point(t), this.min || this.max ? (this.min.x = Math.min(t.x, this.min.x), this.max.x = Math.max(t.x, this.max.x), this.min.y = Math.min(t.y, this.min.y), this.max.y = Math.max(t.y, this.max.y)) : (this.min = t.clone(), this.max = t.clone()), this }, getCenter: function(t) { return new o.Point((this.min.x + this.max.x) / 2, (this.min.y + this.max.y) / 2, t) }, getBottomLeft: function() { return new o.Point(this.min.x, this.max.y) }, getTopRight: function() { return new o.Point(this.max.x, this.min.y) }, getSize: function() { return this.max.subtract(this.min) }, contains: function(t) { var e, i; return t = "number" == typeof t[0] || t instanceof o.Point ? o.point(t) : o.bounds(t), t instanceof o.Bounds ? (e = t.min, i = t.max) : e = i = t, e.x >= this.min.x && i.x <= this.max.x && e.y >= this.min.y && i.y <= this.max.y }, intersects: function(t) { t = o.bounds(t); var e = this.min, - i = this.max, - n = t.min, - s = t.max, - r = s.x >= e.x && n.x <= i.x, - a = s.y >= e.y && n.y <= i.y; return r && a }, overlaps: function(t) { t = o.bounds(t); var e = this.min, - i = this.max, - n = t.min, - s = t.max, - r = s.x > e.x && n.x < i.x, - a = s.y > e.y && n.y < i.y; return r && a }, isValid: function() { return !(!this.min || !this.max) } }, o.bounds = function(t, e) { return !t || t instanceof o.Bounds ? t : new o.Bounds(t, e) }, o.Transformation = function(t, e, i, n) { this._a = t, this._b = e, this._c = i, this._d = n }, o.Transformation.prototype = { transform: function(t, e) { return this._transform(t.clone(), e) }, _transform: function(t, e) { return e = e || 1, t.x = e * (this._a * t.x + this._b), t.y = e * (this._c * t.y + this._d), t }, untransform: function(t, e) { return e = e || 1, new o.Point((t.x / e - this._b) / this._a, (t.y / e - this._d) / this._c) } }, o.DomUtil = { get: function(t) { return "string" == typeof t ? e.getElementById(t) : t }, getStyle: function(t, i) { var n = t.style[i] || t.currentStyle && t.currentStyle[i]; if ((!n || "auto" === n) && e.defaultView) { var o = e.defaultView.getComputedStyle(t, null); - n = o ? o[i] : null } return "auto" === n ? null : n }, create: function(t, i, n) { var o = e.createElement(t); return o.className = i, n && n.appendChild(o), o }, remove: function(t) { var e = t.parentNode; - e && e.removeChild(t) }, empty: function(t) { for (; t.firstChild;) t.removeChild(t.firstChild) }, toFront: function(t) { t.parentNode.appendChild(t) }, toBack: function(t) { var e = t.parentNode; - e.insertBefore(t, e.firstChild) }, hasClass: function(t, e) { if (t.classList !== i) return t.classList.contains(e); var n = o.DomUtil.getClass(t); return n.length > 0 && new RegExp("(^|\\s)" + e + "(\\s|$)").test(n) }, addClass: function(t, e) { if (t.classList !== i) - for (var n = o.Util.splitWords(e), s = 0, r = n.length; r > s; s++) t.classList.add(n[s]); - else if (!o.DomUtil.hasClass(t, e)) { var a = o.DomUtil.getClass(t); - o.DomUtil.setClass(t, (a ? a + " " : "") + e) } }, removeClass: function(t, e) { t.classList !== i ? t.classList.remove(e) : o.DomUtil.setClass(t, o.Util.trim((" " + o.DomUtil.getClass(t) + " ").replace(" " + e + " ", " "))) }, setClass: function(t, e) { t.className.baseVal === i ? t.className = e : t.className.baseVal = e }, getClass: function(t) { return t.className.baseVal === i ? t.className : t.className.baseVal }, setOpacity: function(t, e) { "opacity" in t.style ? t.style.opacity = e : "filter" in t.style && o.DomUtil._setOpacityIE(t, e) }, _setOpacityIE: function(t, e) { var i = !1, - n = "DXImageTransform.Microsoft.Alpha"; try { i = t.filters.item(n) } catch (o) { if (1 === e) return } - e = Math.round(100 * e), i ? (i.Enabled = 100 !== e, i.Opacity = e) : t.style.filter += " progid:" + n + "(opacity=" + e + ")" }, testProp: function(t) { for (var i = e.documentElement.style, n = 0; n < t.length; n++) - if (t[n] in i) return t[n]; - return !1 }, setTransform: function(t, e, i) { var n = e || new o.Point(0, 0); - t.style[o.DomUtil.TRANSFORM] = (o.Browser.ie3d ? "translate(" + n.x + "px," + n.y + "px)" : "translate3d(" + n.x + "px," + n.y + "px,0)") + (i ? " scale(" + i + ")" : "") }, setPosition: function(t, e) { t._leaflet_pos = e, o.Browser.any3d ? o.DomUtil.setTransform(t, e) : (t.style.left = e.x + "px", t.style.top = e.y + "px") }, getPosition: function(t) { return t._leaflet_pos } }, - function() { o.DomUtil.TRANSFORM = o.DomUtil.testProp(["transform", "WebkitTransform", "OTransform", "MozTransform", "msTransform"]); var i = o.DomUtil.TRANSITION = o.DomUtil.testProp(["webkitTransition", "transition", "OTransition", "MozTransition", "msTransition"]); if (o.DomUtil.TRANSITION_END = "webkitTransition" === i || "OTransition" === i ? i + "End" : "transitionend", "onselectstart" in e) o.DomUtil.disableTextSelection = function() { o.DomEvent.on(t, "selectstart", o.DomEvent.preventDefault) }, o.DomUtil.enableTextSelection = function() { o.DomEvent.off(t, "selectstart", o.DomEvent.preventDefault) }; - else { var n = o.DomUtil.testProp(["userSelect", "WebkitUserSelect", "OUserSelect", "MozUserSelect", "msUserSelect"]); - o.DomUtil.disableTextSelection = function() { if (n) { var t = e.documentElement.style; - this._userSelect = t[n], t[n] = "none" } }, o.DomUtil.enableTextSelection = function() { n && (e.documentElement.style[n] = this._userSelect, delete this._userSelect) } } - o.DomUtil.disableImageDrag = function() { o.DomEvent.on(t, "dragstart", o.DomEvent.preventDefault) }, o.DomUtil.enableImageDrag = function() { o.DomEvent.off(t, "dragstart", o.DomEvent.preventDefault) }, o.DomUtil.preventOutline = function(e) { for (; - 1 === e.tabIndex;) e = e.parentNode; - e && e.style && (o.DomUtil.restoreOutline(), this._outlineElement = e, this._outlineStyle = e.style.outline, e.style.outline = "none", o.DomEvent.on(t, "keydown", o.DomUtil.restoreOutline, this)) }, o.DomUtil.restoreOutline = function() { this._outlineElement && (this._outlineElement.style.outline = this._outlineStyle, delete this._outlineElement, delete this._outlineStyle, o.DomEvent.off(t, "keydown", o.DomUtil.restoreOutline, this)) } }(), o.LatLng = function(t, e, n) { if (isNaN(t) || isNaN(e)) throw new Error("Invalid LatLng object: (" + t + ", " + e + ")"); - this.lat = +t, this.lng = +e, n !== i && (this.alt = +n) }, o.LatLng.prototype = { equals: function(t, e) { if (!t) return !1; - t = o.latLng(t); var n = Math.max(Math.abs(this.lat - t.lat), Math.abs(this.lng - t.lng)); return (e === i ? 1e-9 : e) >= n }, toString: function(t) { return "LatLng(" + o.Util.formatNum(this.lat, t) + ", " + o.Util.formatNum(this.lng, t) + ")" }, distanceTo: function(t) { return o.CRS.Earth.distance(this, o.latLng(t)) }, wrap: function() { return o.CRS.Earth.wrapLatLng(this) }, toBounds: function(t) { var e = 180 * t / 40075017, - i = e / Math.cos(Math.PI / 180 * this.lat); return o.latLngBounds([this.lat - e, this.lng - i], [this.lat + e, this.lng + i]) }, clone: function() { return new o.LatLng(this.lat, this.lng, this.alt) } }, o.latLng = function(t, e, n) { return t instanceof o.LatLng ? t : o.Util.isArray(t) && "object" != typeof t[0] ? 3 === t.length ? new o.LatLng(t[0], t[1], t[2]) : 2 === t.length ? new o.LatLng(t[0], t[1]) : null : t === i || null === t ? t : "object" == typeof t && "lat" in t ? new o.LatLng(t.lat, "lng" in t ? t.lng : t.lon, t.alt) : e === i ? null : new o.LatLng(t, e, n) }, o.LatLngBounds = function(t, e) { if (t) - for (var i = e ? [t, e] : t, n = 0, o = i.length; o > n; n++) this.extend(i[n]) }, o.LatLngBounds.prototype = { extend: function(t) { var e, i, n = this._southWest, - s = this._northEast; if (t instanceof o.LatLng) e = t, i = t; - else { if (!(t instanceof o.LatLngBounds)) return t ? this.extend(o.latLng(t) || o.latLngBounds(t)) : this; if (e = t._southWest, i = t._northEast, !e || !i) return this } return n || s ? (n.lat = Math.min(e.lat, n.lat), n.lng = Math.min(e.lng, n.lng), s.lat = Math.max(i.lat, s.lat), s.lng = Math.max(i.lng, s.lng)) : (this._southWest = new o.LatLng(e.lat, e.lng), this._northEast = new o.LatLng(i.lat, i.lng)), this }, pad: function(t) { var e = this._southWest, - i = this._northEast, - n = Math.abs(e.lat - i.lat) * t, - s = Math.abs(e.lng - i.lng) * t; return new o.LatLngBounds(new o.LatLng(e.lat - n, e.lng - s), new o.LatLng(i.lat + n, i.lng + s)) }, getCenter: function() { return new o.LatLng((this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lng + this._northEast.lng) / 2) }, getSouthWest: function() { return this._southWest }, getNorthEast: function() { return this._northEast }, getNorthWest: function() { return new o.LatLng(this.getNorth(), this.getWest()) }, getSouthEast: function() { return new o.LatLng(this.getSouth(), this.getEast()) }, getWest: function() { return this._southWest.lng }, getSouth: function() { return this._southWest.lat }, getEast: function() { return this._northEast.lng }, getNorth: function() { return this._northEast.lat }, contains: function(t) { t = "number" == typeof t[0] || t instanceof o.LatLng ? o.latLng(t) : o.latLngBounds(t); var e, i, n = this._southWest, - s = this._northEast; return t instanceof o.LatLngBounds ? (e = t.getSouthWest(), i = t.getNorthEast()) : e = i = t, e.lat >= n.lat && i.lat <= s.lat && e.lng >= n.lng && i.lng <= s.lng }, intersects: function(t) { t = o.latLngBounds(t); var e = this._southWest, - i = this._northEast, - n = t.getSouthWest(), - s = t.getNorthEast(), - r = s.lat >= e.lat && n.lat <= i.lat, - a = s.lng >= e.lng && n.lng <= i.lng; return r && a }, overlaps: function(t) { t = o.latLngBounds(t); var e = this._southWest, - i = this._northEast, - n = t.getSouthWest(), - s = t.getNorthEast(), - r = s.lat > e.lat && n.lat < i.lat, - a = s.lng > e.lng && n.lng < i.lng; return r && a }, toBBoxString: function() { return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(",") }, equals: function(t) { return t ? (t = o.latLngBounds(t), this._southWest.equals(t.getSouthWest()) && this._northEast.equals(t.getNorthEast())) : !1 }, isValid: function() { return !(!this._southWest || !this._northEast) } }, o.latLngBounds = function(t, e) { return !t || t instanceof o.LatLngBounds ? t : new o.LatLngBounds(t, e) }, o.Projection = {}, o.Projection.LonLat = { project: function(t) { return new o.Point(t.lng, t.lat) }, unproject: function(t) { return new o.LatLng(t.y, t.x) }, bounds: o.bounds([-180, -90], [180, 90]) }, o.Projection.SphericalMercator = { R: 6378137, MAX_LATITUDE: 85.0511287798, project: function(t) { var e = Math.PI / 180, - i = this.MAX_LATITUDE, - n = Math.max(Math.min(i, t.lat), -i), - s = Math.sin(n * e); return new o.Point(this.R * t.lng * e, this.R * Math.log((1 + s) / (1 - s)) / 2) }, unproject: function(t) { var e = 180 / Math.PI; return new o.LatLng((2 * Math.atan(Math.exp(t.y / this.R)) - Math.PI / 2) * e, t.x * e / this.R) }, bounds: function() { var t = 6378137 * Math.PI; return o.bounds([-t, -t], [t, t]) }() }, o.CRS = { latLngToPoint: function(t, e) { var i = this.projection.project(t), - n = this.scale(e); return this.transformation._transform(i, n) }, pointToLatLng: function(t, e) { var i = this.scale(e), - n = this.transformation.untransform(t, i); return this.projection.unproject(n) }, project: function(t) { return this.projection.project(t) }, unproject: function(t) { return this.projection.unproject(t) }, scale: function(t) { return 256 * Math.pow(2, t) }, zoom: function(t) { return Math.log(t / 256) / Math.LN2 }, getProjectedBounds: function(t) { if (this.infinite) return null; var e = this.projection.bounds, - i = this.scale(t), - n = this.transformation.transform(e.min, i), - s = this.transformation.transform(e.max, i); return o.bounds(n, s) }, wrapLatLng: function(t) { var e = this.wrapLng ? o.Util.wrapNum(t.lng, this.wrapLng, !0) : t.lng, - i = this.wrapLat ? o.Util.wrapNum(t.lat, this.wrapLat, !0) : t.lat, - n = t.alt; return o.latLng(i, e, n) } }, o.CRS.Simple = o.extend({}, o.CRS, { projection: o.Projection.LonLat, transformation: new o.Transformation(1, 0, -1, 0), scale: function(t) { return Math.pow(2, t) }, zoom: function(t) { return Math.log(t) / Math.LN2 }, distance: function(t, e) { var i = e.lng - t.lng, - n = e.lat - t.lat; return Math.sqrt(i * i + n * n) }, infinite: !0 }), o.CRS.Earth = o.extend({}, o.CRS, { wrapLng: [-180, 180], R: 6378137, distance: function(t, e) { var i = Math.PI / 180, - n = t.lat * i, - o = e.lat * i, - s = Math.sin(n) * Math.sin(o) + Math.cos(n) * Math.cos(o) * Math.cos((e.lng - t.lng) * i); return this.R * Math.acos(Math.min(s, 1)) } }), o.CRS.EPSG3857 = o.extend({}, o.CRS.Earth, { code: "EPSG:3857", projection: o.Projection.SphericalMercator, transformation: function() { var t = .5 / (Math.PI * o.Projection.SphericalMercator.R); return new o.Transformation(t, .5, -t, .5) }() }), o.CRS.EPSG900913 = o.extend({}, o.CRS.EPSG3857, { code: "EPSG:900913" }), o.CRS.EPSG4326 = o.extend({}, o.CRS.Earth, { code: "EPSG:4326", projection: o.Projection.LonLat, transformation: new o.Transformation(1 / 180, 1, -1 / 180, .5) }), o.Map = o.Evented.extend({ - options: { crs: o.CRS.EPSG3857, fadeAnimation: !0, trackResize: !0, markerZoomAnimation: !0, maxBoundsViscosity: 0, transform3DLimit: 8388608 }, - initialize: function(t, e) { e = o.setOptions(this, e), this._initContainer(t), this._initLayout(), this._onResize = o.bind(this._onResize, this), this._initEvents(), e.maxBounds && this.setMaxBounds(e.maxBounds), e.zoom !== i && (this._zoom = this._limitZoom(e.zoom)), e.center && e.zoom !== i && this.setView(o.latLng(e.center), e.zoom, { reset: !0 }), this._handlers = [], this._layers = {}, this._zoomBoundLayers = {}, this._sizeChanged = !0, this.callInitHooks(), this._addLayers(this.options.layers) }, - setView: function(t, e) { return e = e === i ? this.getZoom() : e, this._resetView(o.latLng(t), e), this }, - setZoom: function(t, e) { return this._loaded ? this.setView(this.getCenter(), t, { zoom: e }) : (this._zoom = t, this) }, - zoomIn: function(t, e) { return this.setZoom(this._zoom + (t || 1), e) }, - zoomOut: function(t, e) { return this.setZoom(this._zoom - (t || 1), e) }, - setZoomAround: function(t, e, i) { var n = this.getZoomScale(e), - s = this.getSize().divideBy(2), - r = t instanceof o.Point ? t : this.latLngToContainerPoint(t), - a = r.subtract(s).multiplyBy(1 - 1 / n), - h = this.containerPointToLatLng(s.add(a)); return this.setView(h, e, { zoom: i }) }, - _getBoundsCenterZoom: function(t, e) { e = e || {}, t = t.getBounds ? t.getBounds() : o.latLngBounds(t); var i = o.point(e.paddingTopLeft || e.padding || [0, 0]), - n = o.point(e.paddingBottomRight || e.padding || [0, 0]), - s = this.getBoundsZoom(t, !1, i.add(n)); - s = e.maxZoom ? Math.min(e.maxZoom, s) : s; var r = n.subtract(i).divideBy(2), - a = this.project(t.getSouthWest(), s), - h = this.project(t.getNorthEast(), s), - l = this.unproject(a.add(h).divideBy(2).add(r), s); return { center: l, zoom: s } }, - fitBounds: function(t, e) { var i = this._getBoundsCenterZoom(t, e); return this.setView(i.center, i.zoom, e) }, - fitWorld: function(t) { return this.fitBounds([ - [-90, -180], - [90, 180] - ], t) }, - panTo: function(t, e) { return this.setView(t, this._zoom, { pan: e }) }, - panBy: function(t) { return this.fire("movestart"), this._rawPanBy(o.point(t)), this.fire("move"), this.fire("moveend") }, - setMaxBounds: function(t) { return (t = o.latLngBounds(t)) ? (this.options.maxBounds && this.off("moveend", this._panInsideMaxBounds), this.options.maxBounds = t, this._loaded && this._panInsideMaxBounds(), this.on("moveend", this._panInsideMaxBounds)) : this.off("moveend", this._panInsideMaxBounds) }, - setMinZoom: function(t) { return this.options.minZoom = t, this._loaded && this.getZoom() < this.options.minZoom ? this.setZoom(t) : this }, - setMaxZoom: function(t) { return this.options.maxZoom = t, this._loaded && this.getZoom() > this.options.maxZoom ? this.setZoom(t) : this }, - panInsideBounds: function(t, e) { this._enforcingBounds = !0; var i = this.getCenter(), - n = this._limitCenter(i, this._zoom, o.latLngBounds(t)); return i.equals(n) ? this : (this.panTo(n, e), this._enforcingBounds = !1, this) }, - invalidateSize: function(t) { if (!this._loaded) return this; - t = o.extend({ animate: !1, pan: !0 }, t === !0 ? { animate: !0 } : t); var e = this.getSize(); - this._sizeChanged = !0, this._lastCenter = null; var i = this.getSize(), - n = e.divideBy(2).round(), - s = i.divideBy(2).round(), - r = n.subtract(s); return r.x || r.y ? (t.animate && t.pan ? this.panBy(r) : (t.pan && this._rawPanBy(r), this.fire("move"), t.debounceMoveend ? (clearTimeout(this._sizeTimer), this._sizeTimer = setTimeout(o.bind(this.fire, this, "moveend"), 200)) : this.fire("moveend")), this.fire("resize", { oldSize: e, newSize: i })) : this }, - stop: function() { return o.Util.cancelAnimFrame(this._flyToFrame), this._panAnim && this._panAnim.stop(), this }, - addHandler: function(t, e) { if (!e) return this; var i = this[t] = new e(this); return this._handlers.push(i), this.options[t] && i.enable(), this }, - remove: function() { this._initEvents(!0); try { delete this._container._leaflet } catch (t) { this._container._leaflet = i } - o.DomUtil.remove(this._mapPane), this._clearControlPos && this._clearControlPos(), this._clearHandlers(), this._loaded && this.fire("unload"); for (var e in this._layers) this._layers[e].remove(); return this }, - createPane: function(t, e) { var i = "leaflet-pane" + (t ? " leaflet-" + t.replace("Pane", "") + "-pane" : ""), - n = o.DomUtil.create("div", i, e || this._mapPane); return t && (this._panes[t] = n), n }, - getCenter: function() { return this._checkIfLoaded(), this._lastCenter && !this._moved() ? this._lastCenter : this.layerPointToLatLng(this._getCenterLayerPoint()) }, - getZoom: function() { return this._zoom }, - getBounds: function() { var t = this.getPixelBounds(), - e = this.unproject(t.getBottomLeft()), - i = this.unproject(t.getTopRight()); return new o.LatLngBounds(e, i) }, - getMinZoom: function() { return this.options.minZoom === i ? this._layersMinZoom || 0 : this.options.minZoom }, - getMaxZoom: function() { return this.options.maxZoom === i ? this._layersMaxZoom === i ? 1 / 0 : this._layersMaxZoom : this.options.maxZoom }, - getBoundsZoom: function(t, e, i) { t = o.latLngBounds(t); var n, s = this.getMinZoom() - (e ? 1 : 0), - r = this.getMaxZoom(), - a = this.getSize(), - h = t.getNorthWest(), - l = t.getSouthEast(), - u = !0; - i = o.point(i || [0, 0]); - do s++, n = this.project(l, s).subtract(this.project(h, s)).add(i).floor(), u = e ? n.x < a.x || n.y < a.y : a.contains(n); while (u && r >= s); return u && e ? null : e ? s : s - 1 }, - getSize: function() { return (!this._size || this._sizeChanged) && (this._size = new o.Point(this._container.clientWidth, this._container.clientHeight), this._sizeChanged = !1), this._size.clone() }, - getPixelBounds: function(t, e) { var i = this._getTopLeftPoint(t, e); return new o.Bounds(i, i.add(this.getSize())) }, - getPixelOrigin: function() { return this._checkIfLoaded(), this._pixelOrigin }, - getPixelWorldBounds: function(t) { return this.options.crs.getProjectedBounds(t === i ? this.getZoom() : t) }, - getPane: function(t) { return "string" == typeof t ? this._panes[t] : t }, - getPanes: function() { return this._panes }, - getContainer: function() { return this._container }, - getZoomScale: function(t, e) { var n = this.options.crs; return e = e === i ? this._zoom : e, n.scale(t) / n.scale(e) }, - getScaleZoom: function(t, e) { var n = this.options.crs; return e = e === i ? this._zoom : e, n.zoom(t * n.scale(e)) }, - project: function(t, e) { return e = e === i ? this._zoom : e, this.options.crs.latLngToPoint(o.latLng(t), e) }, - unproject: function(t, e) { return e = e === i ? this._zoom : e, this.options.crs.pointToLatLng(o.point(t), e) }, - layerPointToLatLng: function(t) { var e = o.point(t).add(this.getPixelOrigin()); return this.unproject(e) }, - latLngToLayerPoint: function(t) { var e = this.project(o.latLng(t))._round(); return e._subtract(this.getPixelOrigin()) }, - wrapLatLng: function(t) { return this.options.crs.wrapLatLng(o.latLng(t)) }, - distance: function(t, e) { return this.options.crs.distance(o.latLng(t), o.latLng(e)) }, - containerPointToLayerPoint: function(t) { return o.point(t).subtract(this._getMapPanePos()) }, - layerPointToContainerPoint: function(t) { return o.point(t).add(this._getMapPanePos()) }, - containerPointToLatLng: function(t) { var e = this.containerPointToLayerPoint(o.point(t)); return this.layerPointToLatLng(e) }, - latLngToContainerPoint: function(t) { return this.layerPointToContainerPoint(this.latLngToLayerPoint(o.latLng(t))) }, - mouseEventToContainerPoint: function(t) { return o.DomEvent.getMousePosition(t, this._container) }, - mouseEventToLayerPoint: function(t) { return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t)) }, - mouseEventToLatLng: function(t) { return this.layerPointToLatLng(this.mouseEventToLayerPoint(t)) }, - _initContainer: function(t) { var e = this._container = o.DomUtil.get(t); if (!e) throw new Error("Map container not found."); if (e._leaflet) throw new Error("Map container is already initialized."); - o.DomEvent.addListener(e, "scroll", this._onScroll, this), e._leaflet = !0 }, - _initLayout: function() { var t = this._container; - this._fadeAnimated = this.options.fadeAnimation && o.Browser.any3d, o.DomUtil.addClass(t, "leaflet-container" + (o.Browser.touch ? " leaflet-touch" : "") + (o.Browser.retina ? " leaflet-retina" : "") + (o.Browser.ielt9 ? " leaflet-oldie" : "") + (o.Browser.safari ? " leaflet-safari" : "") + (this._fadeAnimated ? " leaflet-fade-anim" : "")); var e = o.DomUtil.getStyle(t, "position"); "absolute" !== e && "relative" !== e && "fixed" !== e && (t.style.position = "relative"), this._initPanes(), this._initControlPos && this._initControlPos() }, - _initPanes: function() { var t = this._panes = {}; - this._paneRenderers = {}, this._mapPane = this.createPane("mapPane", this._container), o.DomUtil.setPosition(this._mapPane, new o.Point(0, 0)), this.createPane("tilePane"), this.createPane("shadowPane"), this.createPane("overlayPane"), this.createPane("markerPane"), this.createPane("popupPane"), this.options.markerZoomAnimation || (o.DomUtil.addClass(t.markerPane, "leaflet-zoom-hide"), o.DomUtil.addClass(t.shadowPane, "leaflet-zoom-hide")) }, - _resetView: function(t, e) { o.DomUtil.setPosition(this._mapPane, new o.Point(0, 0)); var i = !this._loaded; - this._loaded = !0, e = this._limitZoom(e); var n = this._zoom !== e; - this._moveStart(n)._move(t, e)._moveEnd(n), this.fire("viewreset"), i && this.fire("load") }, - _moveStart: function(t) { return t && this.fire("zoomstart"), this.fire("movestart") }, - _move: function(t, e, n) { e === i && (e = this._zoom); var o = this._zoom !== e; return this._zoom = e, this._lastCenter = t, this._pixelOrigin = this._getNewPixelOrigin(t), o && this.fire("zoom", n), this.fire("move", n) }, - _moveEnd: function(t) { return t && this.fire("zoomend"), this.fire("moveend") }, - _rawPanBy: function(t) { o.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(t)) }, - _getZoomSpan: function() { return this.getMaxZoom() - this.getMinZoom() }, - _panInsideMaxBounds: function() { this._enforcingBounds || this.panInsideBounds(this.options.maxBounds) }, - _checkIfLoaded: function() { if (!this._loaded) throw new Error("Set map center and zoom first.") }, - _initEvents: function(e) { if (o.DomEvent) { this._targets = {}, this._targets[o.stamp(this._container)] = this; var i = e ? "off" : "on"; - o.DomEvent[i](this._container, "click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress", this._handleDOMEvent, this), this.options.trackResize && o.DomEvent[i](t, "resize", this._onResize, this), o.Browser.any3d && this.options.transform3DLimit && this[i]("moveend", this._onMoveEnd) } }, - _onResize: function() { o.Util.cancelAnimFrame(this._resizeRequest), this._resizeRequest = o.Util.requestAnimFrame(function() { this.invalidateSize({ debounceMoveend: !0 }) }, this) }, - _onScroll: function() { this._container.scrollTop = 0, this._container.scrollLeft = 0 }, - _onMoveEnd: function() { var t = this._getMapPanePos(); - Math.max(Math.abs(t.x), Math.abs(t.y)) >= this.options.transform3DLimit && this._resetView(this.getCenter(), this.getZoom()) }, - _findEventTargets: function(t, e) { for (var i, n = [], s = "mouseout" === e || "mouseover" === e, r = t.target || t.srcElement; r;) { if (i = this._targets[o.stamp(r)], i && i.listens(e, !0)) { if (s && !o.DomEvent._isExternalTarget(r, t)) break; if (n.push(i), s) break } if (r === this._container) break; - r = r.parentNode } return n.length || s || !o.DomEvent._isExternalTarget(r, t) || (n = [this]), n }, - _handleDOMEvent: function(t) { if (this._loaded && !o.DomEvent._skipped(t)) { var e = "keypress" === t.type && 13 === t.keyCode ? "click" : t.type; if ("click" === t.type) { var i = o.Util.extend({}, t); - i.type = "preclick", this._handleDOMEvent(i) } "mousedown" === e && o.DomUtil.preventOutline(t.target || t.srcElement), this._fireDOMEvent(t, e) } }, - _fireDOMEvent: function(t, e, i) { if (!t._stopped && (i = (i || []).concat(this._findEventTargets(t, e)), i.length)) { var n = i[0]; if ("contextmenu" === e && n.listens(e, !0) && o.DomEvent.preventDefault(t), "click" !== t.type && "preclick" !== t.type || t._simulated || !this._draggableMoved(n)) { var s = { originalEvent: t }; if ("keypress" !== t.type) { var r = n instanceof o.Marker; - s.containerPoint = r ? this.latLngToContainerPoint(n.getLatLng()) : this.mouseEventToContainerPoint(t), s.layerPoint = this.containerPointToLayerPoint(s.containerPoint), s.latlng = r ? n.getLatLng() : this.layerPointToLatLng(s.layerPoint) } for (var a = 0; a < i.length; a++) - if (i[a].fire(e, s, !0), s.originalEvent._stopped || i[a].options.nonBubblingEvents && -1 !== o.Util.indexOf(i[a].options.nonBubblingEvents, e)) return } } }, - _draggableMoved: function(t) { return t = t.options.draggable ? t : this, t.dragging && t.dragging.moved() || this.boxZoom && this.boxZoom.moved() }, - _clearHandlers: function() { for (var t = 0, e = this._handlers.length; e > t; t++) this._handlers[t].disable() }, - whenReady: function(t, e) { return this._loaded ? t.call(e || this, { target: this }) : this.on("load", t, e), this }, - _getMapPanePos: function() { return o.DomUtil.getPosition(this._mapPane) || new o.Point(0, 0) }, - _moved: function() { var t = this._getMapPanePos(); return t && !t.equals([0, 0]) }, - _getTopLeftPoint: function(t, e) { var n = t && e !== i ? this._getNewPixelOrigin(t, e) : this.getPixelOrigin(); return n.subtract(this._getMapPanePos()) }, - _getNewPixelOrigin: function(t, e) { var i = this.getSize()._divideBy(2); return this.project(t, e)._subtract(i)._add(this._getMapPanePos())._round() }, - _latLngToNewLayerPoint: function(t, e, i) { - var n = this._getNewPixelOrigin(i, e); - return this.project(t, e)._subtract(n) - }, - _getCenterLayerPoint: function() { return this.containerPointToLayerPoint(this.getSize()._divideBy(2)) }, - _getCenterOffset: function(t) { return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint()) }, - _limitCenter: function(t, e, i) { if (!i) return t; var n = this.project(t, e), - s = this.getSize().divideBy(2), - r = new o.Bounds(n.subtract(s), n.add(s)), - a = this._getBoundsOffset(r, i, e); return this.unproject(n.add(a), e) }, - _limitOffset: function(t, e) { if (!e) return t; var i = this.getPixelBounds(), - n = new o.Bounds(i.min.add(t), i.max.add(t)); return t.add(this._getBoundsOffset(n, e)) }, - _getBoundsOffset: function(t, e, i) { var n = this.project(e.getNorthWest(), i).subtract(t.min), - s = this.project(e.getSouthEast(), i).subtract(t.max), - r = this._rebound(n.x, -s.x), - a = this._rebound(n.y, -s.y); return new o.Point(r, a) }, - _rebound: function(t, e) { return t + e > 0 ? Math.round(t - e) / 2 : Math.max(0, Math.ceil(t)) - Math.max(0, Math.floor(e)) }, - _limitZoom: function(t) { var e = this.getMinZoom(), - i = this.getMaxZoom(); return o.Browser.any3d || (t = Math.round(t)), Math.max(e, Math.min(i, t)) } - }), o.map = function(t, e) { return new o.Map(t, e) }, o.Layer = o.Evented.extend({ options: { pane: "overlayPane", nonBubblingEvents: [] }, addTo: function(t) { return t.addLayer(this), this }, remove: function() { return this.removeFrom(this._map || this._mapToAdd) }, removeFrom: function(t) { return t && t.removeLayer(this), this }, getPane: function(t) { return this._map.getPane(t ? this.options[t] || t : this.options.pane) }, addInteractiveTarget: function(t) { return this._map._targets[o.stamp(t)] = this, this }, removeInteractiveTarget: function(t) { return delete this._map._targets[o.stamp(t)], this }, _layerAdd: function(t) { var e = t.target; - e.hasLayer(this) && (this._map = e, this._zoomAnimated = e._zoomAnimated, this.getEvents && e.on(this.getEvents(), this), this.onAdd(e), this.getAttribution && this._map.attributionControl && this._map.attributionControl.addAttribution(this.getAttribution()), this.fire("add"), e.fire("layeradd", { layer: this })) } }), o.Map.include({ addLayer: function(t) { var e = o.stamp(t); return this._layers[e] ? t : (this._layers[e] = t, t._mapToAdd = this, t.beforeAdd && t.beforeAdd(this), this.whenReady(t._layerAdd, t), this) }, removeLayer: function(t) { var e = o.stamp(t); return this._layers[e] ? (this._loaded && t.onRemove(this), t.getAttribution && this.attributionControl && this.attributionControl.removeAttribution(t.getAttribution()), t.getEvents && this.off(t.getEvents(), t), delete this._layers[e], this._loaded && (this.fire("layerremove", { layer: t }), t.fire("remove")), t._map = t._mapToAdd = null, this) : this }, hasLayer: function(t) { return !!t && o.stamp(t) in this._layers }, eachLayer: function(t, e) { for (var i in this._layers) t.call(e, this._layers[i]); return this }, _addLayers: function(t) { t = t ? o.Util.isArray(t) ? t : [t] : []; for (var e = 0, i = t.length; i > e; e++) this.addLayer(t[e]) }, _addZoomLimit: function(t) { - (isNaN(t.options.maxZoom) || !isNaN(t.options.minZoom)) && (this._zoomBoundLayers[o.stamp(t)] = t, this._updateZoomLevels()) }, _removeZoomLimit: function(t) { var e = o.stamp(t); - this._zoomBoundLayers[e] && (delete this._zoomBoundLayers[e], this._updateZoomLevels()) }, _updateZoomLevels: function() { var t = 1 / 0, - e = -(1 / 0), - n = this._getZoomSpan(); for (var o in this._zoomBoundLayers) { var s = this._zoomBoundLayers[o].options; - t = s.minZoom === i ? t : Math.min(t, s.minZoom), e = s.maxZoom === i ? e : Math.max(e, s.maxZoom) } - this._layersMaxZoom = e === -(1 / 0) ? i : e, this._layersMinZoom = t === 1 / 0 ? i : t, n !== this._getZoomSpan() && this.fire("zoomlevelschange") } }), o.Projection.Mercator = { R: 6378137, R_MINOR: 6356752.314245179, bounds: o.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), project: function(t) { var e = Math.PI / 180, - i = this.R, - n = t.lat * e, - s = this.R_MINOR / i, - r = Math.sqrt(1 - s * s), - a = r * Math.sin(n), - h = Math.tan(Math.PI / 4 - n / 2) / Math.pow((1 - a) / (1 + a), r / 2); return n = -i * Math.log(Math.max(h, 1e-10)), new o.Point(t.lng * e * i, n) }, unproject: function(t) { for (var e, i = 180 / Math.PI, n = this.R, s = this.R_MINOR / n, r = Math.sqrt(1 - s * s), a = Math.exp(-t.y / n), h = Math.PI / 2 - 2 * Math.atan(a), l = 0, u = .1; 15 > l && Math.abs(u) > 1e-7; l++) e = r * Math.sin(h), e = Math.pow((1 - e) / (1 + e), r / 2), u = Math.PI / 2 - 2 * Math.atan(a * e) - h, h += u; return new o.LatLng(h * i, t.x * i / n) } }, o.CRS.EPSG3395 = o.extend({}, o.CRS.Earth, { code: "EPSG:3395", projection: o.Projection.Mercator, transformation: function() { var t = .5 / (Math.PI * o.Projection.Mercator.R); return new o.Transformation(t, .5, -t, .5) }() }), o.GridLayer = o.Layer.extend({ options: { pane: "tilePane", tileSize: 256, opacity: 1, zIndex: 1, updateWhenIdle: o.Browser.mobile, updateInterval: 200, attribution: null, bounds: null, minZoom: 0 }, initialize: function(t) { t = o.setOptions(this, t) }, onAdd: function() { this._initContainer(), this._levels = {}, this._tiles = {}, this._resetView(), this._update() }, beforeAdd: function(t) { t._addZoomLimit(this) }, onRemove: function(t) { o.DomUtil.remove(this._container), t._removeZoomLimit(this), this._container = null, this._tileZoom = null }, bringToFront: function() { return this._map && (o.DomUtil.toFront(this._container), this._setAutoZIndex(Math.max)), this }, bringToBack: function() { return this._map && (o.DomUtil.toBack(this._container), this._setAutoZIndex(Math.min)), this }, getAttribution: function() { return this.options.attribution }, getContainer: function() { return this._container }, setOpacity: function(t) { return this.options.opacity = t, this._updateOpacity(), this }, setZIndex: function(t) { return this.options.zIndex = t, this._updateZIndex(), this }, isLoading: function() { return this._loading }, redraw: function() { return this._map && (this._removeAllTiles(), this._update()), this }, getEvents: function() { var t = { viewreset: this._resetAll, zoom: this._resetView, moveend: this._onMoveEnd }; return this.options.updateWhenIdle || (this._onMove || (this._onMove = o.Util.throttle(this._onMoveEnd, this.options.updateInterval, this)), t.move = this._onMove), this._zoomAnimated && (t.zoomanim = this._animateZoom), t }, createTile: function() { return e.createElement("div") }, getTileSize: function() { var t = this.options.tileSize; return t instanceof o.Point ? t : new o.Point(t, t) }, _updateZIndex: function() { this._container && this.options.zIndex !== i && null !== this.options.zIndex && (this._container.style.zIndex = this.options.zIndex) }, _setAutoZIndex: function(t) { for (var e, i = this.getPane().children, n = -t(-(1 / 0), 1 / 0), o = 0, s = i.length; s > o; o++) e = i[o].style.zIndex, i[o] !== this._container && e && (n = t(n, +e)); - isFinite(n) && (this.options.zIndex = n + t(-1, 1), this._updateZIndex()) }, _updateOpacity: function() { if (this._map && !o.Browser.ielt9 && this._map._fadeAnimated) { o.DomUtil.setOpacity(this._container, this.options.opacity); var t = +new Date, - e = !1, - i = !1; for (var n in this._tiles) { var s = this._tiles[n]; if (s.current && s.loaded) { var r = Math.min(1, (t - s.loaded) / 200); - o.DomUtil.setOpacity(s.el, r), 1 > r ? e = !0 : (s.active && (i = !0), s.active = !0) } } - i && !this._noPrune && this._pruneTiles(), e && (o.Util.cancelAnimFrame(this._fadeFrame), this._fadeFrame = o.Util.requestAnimFrame(this._updateOpacity, this)) } }, _initContainer: function() { this._container || (this._container = o.DomUtil.create("div", "leaflet-layer"), this._updateZIndex(), this.options.opacity < 1 && this._updateOpacity(), this.getPane().appendChild(this._container)) }, _updateLevels: function() { var t = this._tileZoom, - e = this.options.maxZoom; for (var i in this._levels) this._levels[i].el.children.length || i === t ? this._levels[i].el.style.zIndex = e - Math.abs(t - i) : (o.DomUtil.remove(this._levels[i].el), delete this._levels[i]); var n = this._levels[t], - s = this._map; return n || (n = this._levels[t] = {}, n.el = o.DomUtil.create("div", "leaflet-tile-container leaflet-zoom-animated", this._container), n.el.style.zIndex = e, n.origin = s.project(s.unproject(s.getPixelOrigin()), t).round(), n.zoom = t, this._setZoomTransform(n, s.getCenter(), s.getZoom()), o.Util.falseFn(n.el.offsetWidth)), this._level = n, n }, _pruneTiles: function() { var t, e, i = this._map.getZoom(); if (i > this.options.maxZoom || i < this.options.minZoom) return this._removeAllTiles(); for (t in this._tiles) e = this._tiles[t], e.retain = e.current; for (t in this._tiles) - if (e = this._tiles[t], e.current && !e.active) { var n = e.coords; - this._retainParent(n.x, n.y, n.z, n.z - 5) || this._retainChildren(n.x, n.y, n.z, n.z + 2) } - for (t in this._tiles) this._tiles[t].retain || this._removeTile(t) }, _removeAllTiles: function() { for (var t in this._tiles) this._removeTile(t) }, _resetAll: function() { for (var t in this._levels) o.DomUtil.remove(this._levels[t].el), delete this._levels[t]; - this._removeAllTiles(), this._tileZoom = null, this._resetView() }, _retainParent: function(t, e, i, n) { var o = Math.floor(t / 2), - s = Math.floor(e / 2), - r = i - 1, - a = o + ":" + s + ":" + r, - h = this._tiles[a]; return h && h.active ? (h.retain = !0, !0) : (h && h.loaded && (h.retain = !0), r > n ? this._retainParent(o, s, r, n) : !1) }, _retainChildren: function(t, e, i, n) { for (var o = 2 * t; 2 * t + 2 > o; o++) - for (var s = 2 * e; 2 * e + 2 > s; s++) { var r = o + ":" + s + ":" + (i + 1), - a = this._tiles[r]; - a && a.active ? a.retain = !0 : (a && a.loaded && (a.retain = !0), n > i + 1 && this._retainChildren(o, s, i + 1, n)) } }, _resetView: function(t) { var e = t && (t.pinch || t.flyTo); - this._setView(this._map.getCenter(), this._map.getZoom(), e, e) }, _animateZoom: function(t) { this._setView(t.center, t.zoom, !0, t.noUpdate) }, _setView: function(t, e, n, o) { var s = Math.round(e); - (this.options.maxZoom !== i && s > this.options.maxZoom || this.options.minZoom !== i && s < this.options.minZoom) && (s = i); var r = s !== this._tileZoom; - (!o || r) && (this._tileZoom = s, this._abortLoading && this._abortLoading(), this._updateLevels(), this._resetGrid(), s !== i && this._update(t), n || this._pruneTiles(), this._noPrune = !!n), this._setZoomTransforms(t, e) }, _setZoomTransforms: function(t, e) { for (var i in this._levels) this._setZoomTransform(this._levels[i], t, e) }, _setZoomTransform: function(t, e, i) { var n = this._map.getZoomScale(i, t.zoom), - s = t.origin.multiplyBy(n).subtract(this._map._getNewPixelOrigin(e, i)).round(); - o.Browser.any3d ? o.DomUtil.setTransform(t.el, s, n) : o.DomUtil.setPosition(t.el, s) }, _resetGrid: function() { var t = this._map, - e = t.options.crs, - i = this._tileSize = this.getTileSize(), - n = this._tileZoom, - o = this._map.getPixelWorldBounds(this._tileZoom); - o && (this._globalTileRange = this._pxBoundsToTileRange(o)), this._wrapX = e.wrapLng && !this.options.noWrap && [Math.floor(t.project([0, e.wrapLng[0]], n).x / i.x), Math.ceil(t.project([0, e.wrapLng[1]], n).x / i.y)], this._wrapY = e.wrapLat && !this.options.noWrap && [Math.floor(t.project([e.wrapLat[0], 0], n).y / i.x), Math.ceil(t.project([e.wrapLat[1], 0], n).y / i.y)] }, _onMoveEnd: function() { this._map && !this._map._animatingZoom && this._resetView() }, _getTiledPixelBounds: function(t, e, i) { var n = this._map, - s = n.getZoomScale(e, i), - r = n.project(t, i).floor(), - a = n.getSize().divideBy(2 * s); return new o.Bounds(r.subtract(a), r.add(a)) }, _update: function(t) { var n = this._map; if (n) { var s = n.getZoom(); if (t === i && (t = n.getCenter()), this._tileZoom !== i) { var r = this._getTiledPixelBounds(t, s, this._tileZoom), - a = this._pxBoundsToTileRange(r), - h = a.getCenter(), - l = []; for (var u in this._tiles) this._tiles[u].current = !1; if (Math.abs(s - this._tileZoom) > 1) return void this._setView(t, s); for (var c = a.min.y; c <= a.max.y; c++) - for (var d = a.min.x; d <= a.max.x; d++) { var _ = new o.Point(d, c); if (_.z = this._tileZoom, this._isValidTile(_)) { var m = this._tiles[this._tileCoordsToKey(_)]; - m ? m.current = !0 : l.push(_) } } - if (l.sort(function(t, e) { return t.distanceTo(h) - e.distanceTo(h) }), 0 !== l.length) { this._loading || (this._loading = !0, this.fire("loading")); var p = e.createDocumentFragment(); for (d = 0; d < l.length; d++) this._addTile(l[d], p); - this._level.el.appendChild(p) } } } }, _isValidTile: function(t) { var e = this._map.options.crs; if (!e.infinite) { var i = this._globalTileRange; if (!e.wrapLng && (t.x < i.min.x || t.x > i.max.x) || !e.wrapLat && (t.y < i.min.y || t.y > i.max.y)) return !1 } if (!this.options.bounds) return !0; var n = this._tileCoordsToBounds(t); return o.latLngBounds(this.options.bounds).overlaps(n) }, _keyToBounds: function(t) { return this._tileCoordsToBounds(this._keyToTileCoords(t)) }, _tileCoordsToBounds: function(t) { var e = this._map, - i = this.getTileSize(), - n = t.scaleBy(i), - s = n.add(i), - r = e.wrapLatLng(e.unproject(n, t.z)), - a = e.wrapLatLng(e.unproject(s, t.z)); return new o.LatLngBounds(r, a) }, _tileCoordsToKey: function(t) { return t.x + ":" + t.y + ":" + t.z }, _keyToTileCoords: function(t) { var e = t.split(":"), - i = new o.Point(+e[0], +e[1]); return i.z = +e[2], i }, _removeTile: function(t) { var e = this._tiles[t]; - e && (o.DomUtil.remove(e.el), delete this._tiles[t], this.fire("tileunload", { tile: e.el, coords: this._keyToTileCoords(t) })) }, _initTile: function(t) { o.DomUtil.addClass(t, "leaflet-tile"); var e = this.getTileSize(); - t.style.width = e.x + "px", t.style.height = e.y + "px", t.onselectstart = o.Util.falseFn, t.onmousemove = o.Util.falseFn, o.Browser.ielt9 && this.options.opacity < 1 && o.DomUtil.setOpacity(t, this.options.opacity), o.Browser.android && !o.Browser.android23 && (t.style.WebkitBackfaceVisibility = "hidden") }, _addTile: function(t, e) { var i = this._getTilePos(t), - n = this._tileCoordsToKey(t), - s = this.createTile(this._wrapCoords(t), o.bind(this._tileReady, this, t)); - this._initTile(s), this.createTile.length < 2 && o.Util.requestAnimFrame(o.bind(this._tileReady, this, t, null, s)), o.DomUtil.setPosition(s, i), this._tiles[n] = { el: s, coords: t, current: !0 }, e.appendChild(s), this.fire("tileloadstart", { tile: s, coords: t }) }, _tileReady: function(t, e, i) { if (this._map) { e && this.fire("tileerror", { error: e, tile: i, coords: t }); var n = this._tileCoordsToKey(t); - i = this._tiles[n], i && (i.loaded = +new Date, this._map._fadeAnimated ? (o.DomUtil.setOpacity(i.el, 0), o.Util.cancelAnimFrame(this._fadeFrame), this._fadeFrame = o.Util.requestAnimFrame(this._updateOpacity, this)) : (i.active = !0, this._pruneTiles()), o.DomUtil.addClass(i.el, "leaflet-tile-loaded"), this.fire("tileload", { tile: i.el, coords: t }), this._noTilesToLoad() && (this._loading = !1, this.fire("load"))) } }, _getTilePos: function(t) { return t.scaleBy(this.getTileSize()).subtract(this._level.origin) }, _wrapCoords: function(t) { var e = new o.Point(this._wrapX ? o.Util.wrapNum(t.x, this._wrapX) : t.x, this._wrapY ? o.Util.wrapNum(t.y, this._wrapY) : t.y); return e.z = t.z, e }, _pxBoundsToTileRange: function(t) { var e = this.getTileSize(); return new o.Bounds(t.min.unscaleBy(e).floor(), t.max.unscaleBy(e).ceil().subtract([1, 1])) }, _noTilesToLoad: function() { for (var t in this._tiles) - if (!this._tiles[t].loaded) return !1; - return !0 } }), o.gridLayer = function(t) { return new o.GridLayer(t) }, o.TileLayer = o.GridLayer.extend({ options: { maxZoom: 18, subdomains: "abc", errorTileUrl: "", zoomOffset: 0, maxNativeZoom: null, tms: !1, zoomReverse: !1, detectRetina: !1, crossOrigin: !1 }, initialize: function(t, e) { this._url = t, e = o.setOptions(this, e), e.detectRetina && o.Browser.retina && e.maxZoom > 0 && (e.tileSize = Math.floor(e.tileSize / 2), e.zoomOffset++, e.minZoom = Math.max(0, e.minZoom), e.maxZoom--), "string" == typeof e.subdomains && (e.subdomains = e.subdomains.split("")), o.Browser.android || this.on("tileunload", this._onTileRemove) }, setUrl: function(t, e) { return this._url = t, e || this.redraw(), this }, createTile: function(t, i) { var n = e.createElement("img"); return o.DomEvent.on(n, "load", o.bind(this._tileOnLoad, this, i, n)), o.DomEvent.on(n, "error", o.bind(this._tileOnError, this, i, n)), this.options.crossOrigin && (n.crossOrigin = ""), n.alt = "", n.src = this.getTileUrl(t), n }, getTileUrl: function(t) { return o.Util.template(this._url, o.extend({ r: this.options.detectRetina && o.Browser.retina && this.options.maxZoom > 0 ? "@2x" : "", s: this._getSubdomain(t), x: t.x, y: this.options.tms ? this._globalTileRange.max.y - t.y : t.y, z: this._getZoomForUrl() }, this.options)) }, _tileOnLoad: function(t, e) { o.Browser.ielt9 ? setTimeout(o.bind(t, this, null, e), 0) : t(null, e) }, _tileOnError: function(t, e, i) { var n = this.options.errorTileUrl; - n && (e.src = n), t(i, e) }, getTileSize: function() { var t = this._map, - e = o.GridLayer.prototype.getTileSize.call(this), - i = this._tileZoom + this.options.zoomOffset, - n = this.options.maxNativeZoom; return null !== n && i > n ? e.divideBy(t.getZoomScale(n, i)).round() : e }, _onTileRemove: function(t) { t.tile.onload = null }, _getZoomForUrl: function() { var t = this.options, - e = this._tileZoom; return t.zoomReverse && (e = t.maxZoom - e), e += t.zoomOffset, null !== t.maxNativeZoom ? Math.min(e, t.maxNativeZoom) : e }, _getSubdomain: function(t) { var e = Math.abs(t.x + t.y) % this.options.subdomains.length; return this.options.subdomains[e] }, _abortLoading: function() { var t, e; for (t in this._tiles) this._tiles[t].coords.z !== this._tileZoom && (e = this._tiles[t].el, e.onload = o.Util.falseFn, e.onerror = o.Util.falseFn, e.complete || (e.src = o.Util.emptyImageUrl, o.DomUtil.remove(e))) } }), o.tileLayer = function(t, e) { return new o.TileLayer(t, e) }, o.TileLayer.WMS = o.TileLayer.extend({ defaultWmsParams: { service: "WMS", request: "GetMap", version: "1.1.1", layers: "", styles: "", format: "image/jpeg", transparent: !1 }, options: { crs: null, uppercase: !1 }, initialize: function(t, e) { this._url = t; var i = o.extend({}, this.defaultWmsParams); for (var n in e) n in this.options || (i[n] = e[n]); - e = o.setOptions(this, e), i.width = i.height = e.tileSize * (e.detectRetina && o.Browser.retina ? 2 : 1), this.wmsParams = i }, onAdd: function(t) { this._crs = this.options.crs || t.options.crs, this._wmsVersion = parseFloat(this.wmsParams.version); var e = this._wmsVersion >= 1.3 ? "crs" : "srs"; - this.wmsParams[e] = this._crs.code, o.TileLayer.prototype.onAdd.call(this, t) }, getTileUrl: function(t) { var e = this._tileCoordsToBounds(t), - i = this._crs.project(e.getNorthWest()), - n = this._crs.project(e.getSouthEast()), - s = (this._wmsVersion >= 1.3 && this._crs === o.CRS.EPSG4326 ? [n.y, i.x, i.y, n.x] : [i.x, n.y, n.x, i.y]).join(","), - r = o.TileLayer.prototype.getTileUrl.call(this, t); return r + o.Util.getParamString(this.wmsParams, r, this.options.uppercase) + (this.options.uppercase ? "&BBOX=" : "&bbox=") + s }, setParams: function(t, e) { return o.extend(this.wmsParams, t), e || this.redraw(), this } }), o.tileLayer.wms = function(t, e) { return new o.TileLayer.WMS(t, e) }, o.ImageOverlay = o.Layer.extend({ options: { opacity: 1, alt: "", interactive: !1 }, initialize: function(t, e, i) { this._url = t, this._bounds = o.latLngBounds(e), o.setOptions(this, i) }, onAdd: function() { this._image || (this._initImage(), this.options.opacity < 1 && this._updateOpacity()), this.options.interactive && (o.DomUtil.addClass(this._image, "leaflet-interactive"), this.addInteractiveTarget(this._image)), this.getPane().appendChild(this._image), this._reset() }, onRemove: function() { o.DomUtil.remove(this._image), this.options.interactive && this.removeInteractiveTarget(this._image) }, setOpacity: function(t) { return this.options.opacity = t, this._image && this._updateOpacity(), this }, setStyle: function(t) { return t.opacity && this.setOpacity(t.opacity), this }, bringToFront: function() { return this._map && o.DomUtil.toFront(this._image), this }, bringToBack: function() { return this._map && o.DomUtil.toBack(this._image), this }, setUrl: function(t) { return this._url = t, this._image && (this._image.src = t), this }, setBounds: function(t) { return this._bounds = t, this._map && this._reset(), this }, getAttribution: function() { return this.options.attribution }, getEvents: function() { var t = { zoom: this._reset, viewreset: this._reset }; return this._zoomAnimated && (t.zoomanim = this._animateZoom), t }, getBounds: function() { return this._bounds }, getElement: function() { return this._image }, _initImage: function() { var t = this._image = o.DomUtil.create("img", "leaflet-image-layer " + (this._zoomAnimated ? "leaflet-zoom-animated" : "")); - t.onselectstart = o.Util.falseFn, t.onmousemove = o.Util.falseFn, t.onload = o.bind(this.fire, this, "load"), this.options.crossOrigin && (t.crossOrigin = ""), t.src = this._url, t.alt = this.options.alt }, _animateZoom: function(t) { var e = this._map.getZoomScale(t.zoom), - i = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), t.zoom, t.center); - o.DomUtil.setTransform(this._image, i, e) }, _reset: function() { var t = this._image, - e = new o.Bounds(this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getSouthEast())), - i = e.getSize(); - o.DomUtil.setPosition(t, e.min), t.style.width = i.x + "px", t.style.height = i.y + "px" }, _updateOpacity: function() { o.DomUtil.setOpacity(this._image, this.options.opacity) } }), o.imageOverlay = function(t, e, i) { return new o.ImageOverlay(t, e, i) }, o.Icon = o.Class.extend({ initialize: function(t) { o.setOptions(this, t) }, createIcon: function(t) { return this._createIcon("icon", t) }, createShadow: function(t) { return this._createIcon("shadow", t) }, _createIcon: function(t, e) { var i = this._getIconUrl(t); if (!i) { if ("icon" === t) throw new Error("iconUrl not set in Icon options (see the docs)."); return null } var n = this._createImg(i, e && "IMG" === e.tagName ? e : null); return this._setIconStyles(n, t), n }, _setIconStyles: function(t, e) { var i = this.options, - n = o.point(i[e + "Size"]), - s = o.point("shadow" === e && i.shadowAnchor || i.iconAnchor || n && n.divideBy(2, !0)); - t.className = "leaflet-marker-" + e + " " + (i.className || ""), s && (t.style.marginLeft = -s.x + "px", t.style.marginTop = -s.y + "px"), n && (t.style.width = n.x + "px", t.style.height = n.y + "px") }, _createImg: function(t, i) { return i = i || e.createElement("img"), i.src = t, i }, _getIconUrl: function(t) { return o.Browser.retina && this.options[t + "RetinaUrl"] || this.options[t + "Url"] } }), o.icon = function(t) { return new o.Icon(t) }, o.Icon.Default = o.Icon.extend({ options: { iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }, _getIconUrl: function(t) { var e = t + "Url"; if (this.options[e]) return this.options[e]; var i = o.Icon.Default.imagePath; if (!i) throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually."); return i + "/marker-" + t + (o.Browser.retina && "icon" === t ? "-2x" : "") + ".png" } }), o.Icon.Default.imagePath = function() { var t, i, n, o, s = e.getElementsByTagName("script"), - r = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/; for (t = 0, i = s.length; i > t; t++) - if (n = s[t].src || "", n.match(r)) return o = n.split(r)[0], (o ? o + "/" : "") + "images" }(), o.Marker = o.Layer.extend({ options: { pane: "markerPane", nonBubblingEvents: ["click", "dblclick", "mouseover", "mouseout", "contextmenu"], icon: new o.Icon.Default, interactive: !0, keyboard: !0, zIndexOffset: 0, opacity: 1, riseOffset: 250 }, initialize: function(t, e) { o.setOptions(this, e), this._latlng = o.latLng(t) }, onAdd: function(t) { this._zoomAnimated = this._zoomAnimated && t.options.markerZoomAnimation, this._initIcon(), this.update() }, onRemove: function() { this.dragging && this.dragging.enabled() && (this.options.draggable = !0, this.dragging.removeHooks()), this._removeIcon(), this._removeShadow() }, getEvents: function() { var t = { zoom: this.update, viewreset: this.update }; return this._zoomAnimated && (t.zoomanim = this._animateZoom), t }, getLatLng: function() { return this._latlng }, setLatLng: function(t) { var e = this._latlng; return this._latlng = o.latLng(t), this.update(), this.fire("move", { oldLatLng: e, latlng: this._latlng }) }, setZIndexOffset: function(t) { return this.options.zIndexOffset = t, this.update() }, setIcon: function(t) { return this.options.icon = t, this._map && (this._initIcon(), this.update()), this._popup && this.bindPopup(this._popup, this._popup.options), this }, getElement: function() { return this._icon }, update: function() { if (this._icon) { var t = this._map.latLngToLayerPoint(this._latlng).round(); - this._setPos(t) } return this }, _initIcon: function() { var t = this.options, - e = "leaflet-zoom-" + (this._zoomAnimated ? "animated" : "hide"), - i = t.icon.createIcon(this._icon), - n = !1; - i !== this._icon && (this._icon && this._removeIcon(), n = !0, t.title && (i.title = t.title), t.alt && (i.alt = t.alt)), o.DomUtil.addClass(i, e), t.keyboard && (i.tabIndex = "0"), this._icon = i, t.riseOnHover && this.on({ mouseover: this._bringToFront, mouseout: this._resetZIndex }); var s = t.icon.createShadow(this._shadow), - r = !1; - s !== this._shadow && (this._removeShadow(), r = !0), s && o.DomUtil.addClass(s, e), this._shadow = s, t.opacity < 1 && this._updateOpacity(), n && (this.getPane().appendChild(this._icon), this._initInteraction()), s && r && this.getPane("shadowPane").appendChild(this._shadow) }, _removeIcon: function() { this.options.riseOnHover && this.off({ mouseover: this._bringToFront, mouseout: this._resetZIndex }), o.DomUtil.remove(this._icon), this.removeInteractiveTarget(this._icon), this._icon = null }, _removeShadow: function() { this._shadow && o.DomUtil.remove(this._shadow), this._shadow = null }, _setPos: function(t) { o.DomUtil.setPosition(this._icon, t), this._shadow && o.DomUtil.setPosition(this._shadow, t), this._zIndex = t.y + this.options.zIndexOffset, this._resetZIndex() }, _updateZIndex: function(t) { this._icon.style.zIndex = this._zIndex + t }, _animateZoom: function(t) { var e = this._map._latLngToNewLayerPoint(this._latlng, t.zoom, t.center).round(); - this._setPos(e) }, _initInteraction: function() { if (this.options.interactive && (o.DomUtil.addClass(this._icon, "leaflet-interactive"), this.addInteractiveTarget(this._icon), o.Handler.MarkerDrag)) { var t = this.options.draggable; - this.dragging && (t = this.dragging.enabled(), this.dragging.disable()), this.dragging = new o.Handler.MarkerDrag(this), t && this.dragging.enable() } }, setOpacity: function(t) { return this.options.opacity = t, this._map && this._updateOpacity(), this }, _updateOpacity: function() { var t = this.options.opacity; - o.DomUtil.setOpacity(this._icon, t), this._shadow && o.DomUtil.setOpacity(this._shadow, t) }, _bringToFront: function() { this._updateZIndex(this.options.riseOffset) }, _resetZIndex: function() { this._updateZIndex(0) } }), o.marker = function(t, e) { return new o.Marker(t, e) }, o.DivIcon = o.Icon.extend({ options: { iconSize: [12, 12], className: "leaflet-div-icon", html: !1 }, createIcon: function(t) { var i = t && "DIV" === t.tagName ? t : e.createElement("div"), - n = this.options; return i.innerHTML = n.html !== !1 ? n.html : "", n.bgPos && (i.style.backgroundPosition = -n.bgPos.x + "px " + -n.bgPos.y + "px"), this._setIconStyles(i, "icon"), i }, createShadow: function() { return null } }), o.divIcon = function(t) { return new o.DivIcon(t) }, o.Map.mergeOptions({ closePopupOnClick: !0 }), o.Popup = o.Layer.extend({ options: { pane: "popupPane", minWidth: 50, maxWidth: 300, offset: [0, 7], autoPan: !0, autoPanPadding: [5, 5], closeButton: !0, autoClose: !0, zoomAnimation: !0 }, initialize: function(t, e) { o.setOptions(this, t), this._source = e }, onAdd: function(t) { this._zoomAnimated = this._zoomAnimated && this.options.zoomAnimation, this._container || this._initLayout(), t._fadeAnimated && o.DomUtil.setOpacity(this._container, 0), clearTimeout(this._removeTimeout), this.getPane().appendChild(this._container), this.update(), t._fadeAnimated && o.DomUtil.setOpacity(this._container, 1), t.fire("popupopen", { popup: this }), this._source && this._source.fire("popupopen", { popup: this }, !0) }, openOn: function(t) { return t.openPopup(this), this }, onRemove: function(t) { t._fadeAnimated ? (o.DomUtil.setOpacity(this._container, 0), this._removeTimeout = setTimeout(o.bind(o.DomUtil.remove, o.DomUtil, this._container), 200)) : o.DomUtil.remove(this._container), t.fire("popupclose", { popup: this }), this._source && this._source.fire("popupclose", { popup: this }, !0) }, getLatLng: function() { return this._latlng }, setLatLng: function(t) { return this._latlng = o.latLng(t), this._map && (this._updatePosition(), this._adjustPan()), this }, getContent: function() { return this._content }, setContent: function(t) { return this._content = t, this.update(), this }, getElement: function() { return this._container }, update: function() { this._map && (this._container.style.visibility = "hidden", this._updateContent(), this._updateLayout(), this._updatePosition(), this._container.style.visibility = "", this._adjustPan()) }, getEvents: function() { var t = { zoom: this._updatePosition, viewreset: this._updatePosition }; return this._zoomAnimated && (t.zoomanim = this._animateZoom), ("closeOnClick" in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) && (t.preclick = this._close), this.options.keepInView && (t.moveend = this._adjustPan), t }, isOpen: function() { return !!this._map && this._map.hasLayer(this) }, bringToFront: function() { return this._map && o.DomUtil.toFront(this._container), this }, bringToBack: function() { return this._map && o.DomUtil.toBack(this._container), this }, _close: function() { this._map && this._map.closePopup(this) }, _initLayout: function() { var t = "leaflet-popup", - e = this._container = o.DomUtil.create("div", t + " " + (this.options.className || "") + " leaflet-zoom-" + (this._zoomAnimated ? "animated" : "hide")); if (this.options.closeButton) { var i = this._closeButton = o.DomUtil.create("a", t + "-close-button", e); - i.href = "#close", i.innerHTML = "×", o.DomEvent.on(i, "click", this._onCloseButtonClick, this) } var n = this._wrapper = o.DomUtil.create("div", t + "-content-wrapper", e); - this._contentNode = o.DomUtil.create("div", t + "-content", n), o.DomEvent.disableClickPropagation(n).disableScrollPropagation(this._contentNode).on(n, "contextmenu", o.DomEvent.stopPropagation), this._tipContainer = o.DomUtil.create("div", t + "-tip-container", e), this._tip = o.DomUtil.create("div", t + "-tip", this._tipContainer) }, _updateContent: function() { if (this._content) { var t = this._contentNode, - e = "function" == typeof this._content ? this._content(this._source || this) : this._content; if ("string" == typeof e) t.innerHTML = e; - else { for (; t.hasChildNodes();) t.removeChild(t.firstChild); - t.appendChild(e) } - this.fire("contentupdate") } }, _updateLayout: function() { var t = this._contentNode, - e = t.style; - e.width = "", e.whiteSpace = "nowrap"; var i = t.offsetWidth; - i = Math.min(i, this.options.maxWidth), i = Math.max(i, this.options.minWidth), e.width = i + 1 + "px", e.whiteSpace = "", e.height = ""; var n = t.offsetHeight, - s = this.options.maxHeight, - r = "leaflet-popup-scrolled"; - s && n > s ? (e.height = s + "px", o.DomUtil.addClass(t, r)) : o.DomUtil.removeClass(t, r), this._containerWidth = this._container.offsetWidth }, _updatePosition: function() { if (this._map) { var t = this._map.latLngToLayerPoint(this._latlng), - e = o.point(this.options.offset); - this._zoomAnimated ? o.DomUtil.setPosition(this._container, t) : e = e.add(t); var i = this._containerBottom = -e.y, - n = this._containerLeft = -Math.round(this._containerWidth / 2) + e.x; - this._container.style.bottom = i + "px", this._container.style.left = n + "px" } }, _animateZoom: function(t) { var e = this._map._latLngToNewLayerPoint(this._latlng, t.zoom, t.center); - o.DomUtil.setPosition(this._container, e) }, _adjustPan: function() { if (!(!this.options.autoPan || this._map._panAnim && this._map._panAnim._inProgress)) { var t = this._map, - e = this._container.offsetHeight, - i = this._containerWidth, - n = new o.Point(this._containerLeft, -e - this._containerBottom); - this._zoomAnimated && n._add(o.DomUtil.getPosition(this._container)); var s = t.layerPointToContainerPoint(n), - r = o.point(this.options.autoPanPadding), - a = o.point(this.options.autoPanPaddingTopLeft || r), - h = o.point(this.options.autoPanPaddingBottomRight || r), - l = t.getSize(), - u = 0, - c = 0; - s.x + i + h.x > l.x && (u = s.x + i - l.x + h.x), s.x - u - a.x < 0 && (u = s.x - a.x), s.y + e + h.y > l.y && (c = s.y + e - l.y + h.y), s.y - c - a.y < 0 && (c = s.y - a.y), (u || c) && t.fire("autopanstart").panBy([u, c]) } }, _onCloseButtonClick: function(t) { this._close(), o.DomEvent.stop(t) } }), o.popup = function(t, e) { return new o.Popup(t, e) }, o.Map.include({ openPopup: function(t, e, i) { return t instanceof o.Popup || (t = new o.Popup(i).setContent(t)), e && t.setLatLng(e), this.hasLayer(t) ? this : (this._popup && this._popup.options.autoClose && this.closePopup(), this._popup = t, this.addLayer(t)) }, closePopup: function(t) { return t && t !== this._popup || (t = this._popup, this._popup = null), t && this.removeLayer(t), this } }), o.Layer.include({ bindPopup: function(t, e) { return t instanceof o.Popup ? (o.setOptions(t, e), this._popup = t, t._source = this) : ((!this._popup || e) && (this._popup = new o.Popup(e, this)), this._popup.setContent(t)), this._popupHandlersAdded || (this.on({ click: this._openPopup, remove: this.closePopup, move: this._movePopup }), this._popupHandlersAdded = !0), this._originalPopupOffset = this._popup.options.offset, this }, unbindPopup: function() { return this._popup && (this.off({ click: this._openPopup, remove: this.closePopup, move: this._movePopup }), this._popupHandlersAdded = !1, this._popup = null), this }, openPopup: function(t, e) { if (t instanceof o.Layer || (e = t, t = this), t instanceof o.FeatureGroup) - for (var i in this._layers) { t = this._layers[i]; break } - return e || (e = t.getCenter ? t.getCenter() : t.getLatLng()), this._popup && this._map && (this._popup.options.offset = this._popupAnchor(t), this._popup._source = t, this._popup.update(), this._map.openPopup(this._popup, e)), this }, closePopup: function() { return this._popup && this._popup._close(), this }, togglePopup: function(t) { return this._popup && (this._popup._map ? this.closePopup() : this.openPopup(t)), this }, isPopupOpen: function() { return this._popup.isOpen() }, setPopupContent: function(t) { return this._popup && this._popup.setContent(t), this }, getPopup: function() { return this._popup }, _openPopup: function(t) { var e = t.layer || t.target; if (this._popup && this._map) return e instanceof o.Path ? void this.openPopup(t.layer || t.target, t.latlng) : void(this._map.hasLayer(this._popup) && this._popup._source === e ? this.closePopup() : this.openPopup(e, t.latlng)) }, _popupAnchor: function(t) { var e = t._getPopupAnchor ? t._getPopupAnchor() : [0, 0], - i = this._originalPopupOffset || o.Popup.prototype.options.offset; return o.point(e).add(i) }, _movePopup: function(t) { this._popup.setLatLng(t.latlng) } }), o.Marker.include({ _getPopupAnchor: function() { return this.options.icon.options.popupAnchor || [0, 0] } }), o.LayerGroup = o.Layer.extend({ - initialize: function(t) { this._layers = {}; var e, i; if (t) - for (e = 0, i = t.length; i > e; e++) this.addLayer(t[e]) }, - addLayer: function(t) { var e = this.getLayerId(t); return this._layers[e] = t, this._map && this._map.addLayer(t), this }, - removeLayer: function(t) { - var e = t in this._layers ? t : this.getLayerId(t); - return this._map && this._layers[e] && this._map.removeLayer(this._layers[e]), - delete this._layers[e], this - }, - hasLayer: function(t) { return !!t && (t in this._layers || this.getLayerId(t) in this._layers) }, - clearLayers: function() { for (var t in this._layers) this.removeLayer(this._layers[t]); return this }, - invoke: function(t) { var e, i, n = Array.prototype.slice.call(arguments, 1); for (e in this._layers) i = this._layers[e], i[t] && i[t].apply(i, n); return this }, - onAdd: function(t) { for (var e in this._layers) t.addLayer(this._layers[e]) }, - onRemove: function(t) { for (var e in this._layers) t.removeLayer(this._layers[e]) }, - eachLayer: function(t, e) { for (var i in this._layers) t.call(e, this._layers[i]); return this }, - getLayer: function(t) { return this._layers[t] }, - getLayers: function() { var t = []; for (var e in this._layers) t.push(this._layers[e]); return t }, - setZIndex: function(t) { return this.invoke("setZIndex", t) }, - getLayerId: function(t) { return o.stamp(t) } - }), o.layerGroup = function(t) { return new o.LayerGroup(t) }, o.FeatureGroup = o.LayerGroup.extend({ addLayer: function(t) { return this.hasLayer(t) ? this : (t.addEventParent(this), o.LayerGroup.prototype.addLayer.call(this, t), this.fire("layeradd", { layer: t })) }, removeLayer: function(t) { return this.hasLayer(t) ? (t in this._layers && (t = this._layers[t]), t.removeEventParent(this), o.LayerGroup.prototype.removeLayer.call(this, t), this.fire("layerremove", { layer: t })) : this }, setStyle: function(t) { return this.invoke("setStyle", t) }, bringToFront: function() { return this.invoke("bringToFront") }, bringToBack: function() { return this.invoke("bringToBack") }, getBounds: function() { var t = new o.LatLngBounds; for (var e in this._layers) { var i = this._layers[e]; - t.extend(i.getBounds ? i.getBounds() : i.getLatLng()) } return t } }), o.featureGroup = function(t) { return new o.FeatureGroup(t) }, o.Renderer = o.Layer.extend({ options: { padding: .1 }, initialize: function(t) { o.setOptions(this, t), o.stamp(this) }, onAdd: function() { this._container || (this._initContainer(), this._zoomAnimated && o.DomUtil.addClass(this._container, "leaflet-zoom-animated")), this.getPane().appendChild(this._container), this._update() }, onRemove: function() { o.DomUtil.remove(this._container) }, getEvents: function() { var t = { viewreset: this._reset, zoomstart: this._onZoomStart, zoom: this._onZoom, moveend: this._update }; return this._zoomAnimated && (t.zoomanim = this._onAnimZoom), t }, _onAnimZoom: function(t) { this._updateTransform(t.center, t.zoom) }, _onZoom: function() { this._updateTransform(this._map.getCenter(), this._map.getZoom()) }, _onZoomStart: function() { this._update() }, _updateTransform: function(t, e) { var i = this._map.getZoomScale(e, this._zoom), - n = o.DomUtil.getPosition(this._container), - s = this._map.getSize().multiplyBy(.5 + this.options.padding), - r = this._map.project(this._center, e), - a = this._map.project(t, e), - h = a.subtract(r), - l = s.multiplyBy(-i).add(n).add(s).subtract(h); - o.DomUtil.setTransform(this._container, l, i) }, _reset: function() { this._update(), this._updateTransform(this._center, this._zoom) }, _update: function() { var t = this.options.padding, - e = this._map.getSize(), - i = this._map.containerPointToLayerPoint(e.multiplyBy(-t)).round(); - this._bounds = new o.Bounds(i, i.add(e.multiplyBy(1 + 2 * t)).round()), this._center = this._map.getCenter(), this._zoom = this._map.getZoom() } }), o.Map.include({ getRenderer: function(t) { var e = t.options.renderer || this._getPaneRenderer(t.options.pane) || this.options.renderer || this._renderer; return e || (e = this._renderer = this.options.preferCanvas && o.canvas() || o.svg()), this.hasLayer(e) || this.addLayer(e), e }, _getPaneRenderer: function(t) { if ("overlayPane" === t || t === i) return !1; var e = this._paneRenderers[t]; return e === i && (e = o.SVG && o.svg({ pane: t }) || o.Canvas && o.canvas({ pane: t }), this._paneRenderers[t] = e), e } }), o.Path = o.Layer.extend({ options: { stroke: !0, color: "#3388ff", weight: 3, opacity: 1, lineCap: "round", lineJoin: "round", fillOpacity: .2, fillRule: "evenodd", interactive: !0 }, beforeAdd: function(t) { this._renderer = t.getRenderer(this) }, onAdd: function() { this._renderer._initPath(this), this._reset(), this._renderer._addPath(this) }, onRemove: function() { this._renderer._removePath(this) }, getEvents: function() { return { zoomend: this._project, moveend: this._update, viewreset: this._reset } }, redraw: function() { return this._map && this._renderer._updatePath(this), this }, setStyle: function(t) { return o.setOptions(this, t), this._renderer && this._renderer._updateStyle(this), this }, bringToFront: function() { return this._renderer && this._renderer._bringToFront(this), this }, bringToBack: function() { return this._renderer && this._renderer._bringToBack(this), this }, getElement: function() { return this._path }, _reset: function() { this._project(), this._update() }, _clickTolerance: function() { return (this.options.stroke ? this.options.weight / 2 : 0) + (o.Browser.touch ? 10 : 0) } }), o.LineUtil = { simplify: function(t, e) { if (!e || !t.length) return t.slice(); var i = e * e; return t = this._reducePoints(t, i), t = this._simplifyDP(t, i) }, pointToSegmentDistance: function(t, e, i) { return Math.sqrt(this._sqClosestPointOnSegment(t, e, i, !0)) }, closestPointOnSegment: function(t, e, i) { return this._sqClosestPointOnSegment(t, e, i) }, _simplifyDP: function(t, e) { var n = t.length, - o = typeof Uint8Array != i + "" ? Uint8Array : Array, - s = new o(n); - s[0] = s[n - 1] = 1, this._simplifyDPStep(t, s, e, 0, n - 1); var r, a = []; for (r = 0; n > r; r++) s[r] && a.push(t[r]); return a }, _simplifyDPStep: function(t, e, i, n, o) { var s, r, a, h = 0; for (r = n + 1; o - 1 >= r; r++) a = this._sqClosestPointOnSegment(t[r], t[n], t[o], !0), a > h && (s = r, h = a); - h > i && (e[s] = 1, this._simplifyDPStep(t, e, i, n, s), this._simplifyDPStep(t, e, i, s, o)) }, _reducePoints: function(t, e) { for (var i = [t[0]], n = 1, o = 0, s = t.length; s > n; n++) this._sqDist(t[n], t[o]) > e && (i.push(t[n]), o = n); return s - 1 > o && i.push(t[s - 1]), i }, clipSegment: function(t, e, i, n, o) { var s, r, a, h = n ? this._lastCode : this._getBitCode(t, i), - l = this._getBitCode(e, i); for (this._lastCode = l;;) { if (!(h | l)) return [t, e]; if (h & l) return !1; - s = h || l, r = this._getEdgeIntersection(t, e, s, i, o), a = this._getBitCode(r, i), s === h ? (t = r, h = a) : (e = r, l = a) } }, _getEdgeIntersection: function(t, e, i, n, s) { var r, a, h = e.x - t.x, - l = e.y - t.y, - u = n.min, - c = n.max; return 8 & i ? (r = t.x + h * (c.y - t.y) / l, a = c.y) : 4 & i ? (r = t.x + h * (u.y - t.y) / l, a = u.y) : 2 & i ? (r = c.x, a = t.y + l * (c.x - t.x) / h) : 1 & i && (r = u.x, a = t.y + l * (u.x - t.x) / h), new o.Point(r, a, s) }, _getBitCode: function(t, e) { var i = 0; return t.x < e.min.x ? i |= 1 : t.x > e.max.x && (i |= 2), t.y < e.min.y ? i |= 4 : t.y > e.max.y && (i |= 8), i }, _sqDist: function(t, e) { var i = e.x - t.x, - n = e.y - t.y; return i * i + n * n }, _sqClosestPointOnSegment: function(t, e, i, n) { var s, r = e.x, - a = e.y, - h = i.x - r, - l = i.y - a, - u = h * h + l * l; return u > 0 && (s = ((t.x - r) * h + (t.y - a) * l) / u, s > 1 ? (r = i.x, a = i.y) : s > 0 && (r += h * s, a += l * s)), h = t.x - r, l = t.y - a, n ? h * h + l * l : new o.Point(r, a) } }, o.Polyline = o.Path.extend({ options: { smoothFactor: 1 }, initialize: function(t, e) { o.setOptions(this, e), this._setLatLngs(t) }, getLatLngs: function() { return this._latlngs }, setLatLngs: function(t) { return this._setLatLngs(t), this.redraw() }, isEmpty: function() { return !this._latlngs.length }, closestLayerPoint: function(t) { for (var e, i, n = 1 / 0, s = null, r = o.LineUtil._sqClosestPointOnSegment, a = 0, h = this._parts.length; h > a; a++) - for (var l = this._parts[a], u = 1, c = l.length; c > u; u++) { e = l[u - 1], i = l[u]; var d = r(t, e, i, !0); - n > d && (n = d, s = r(t, e, i)) } - return s && (s.distance = Math.sqrt(n)), s }, getCenter: function() { var t, e, i, n, o, s, r, a = this._rings[0], - h = a.length; if (!h) return null; for (t = 0, e = 0; h - 1 > t; t++) e += a[t].distanceTo(a[t + 1]) / 2; if (0 === e) return this._map.layerPointToLatLng(a[0]); for (t = 0, n = 0; h - 1 > t; t++) - if (o = a[t], s = a[t + 1], i = o.distanceTo(s), n += i, n > e) return r = (n - e) / i, this._map.layerPointToLatLng([s.x - r * (s.x - o.x), s.y - r * (s.y - o.y)]) }, getBounds: function() { return this._bounds }, addLatLng: function(t, e) { return e = e || this._defaultShape(), t = o.latLng(t), e.push(t), this._bounds.extend(t), this.redraw() }, _setLatLngs: function(t) { this._bounds = new o.LatLngBounds, this._latlngs = this._convertLatLngs(t) }, _defaultShape: function() { return o.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0] }, _convertLatLngs: function(t) { for (var e = [], i = o.Polyline._flat(t), n = 0, s = t.length; s > n; n++) i ? (e[n] = o.latLng(t[n]), this._bounds.extend(e[n])) : e[n] = this._convertLatLngs(t[n]); return e }, _project: function() { this._rings = [], this._projectLatlngs(this._latlngs, this._rings); var t = this._clickTolerance(), - e = new o.Point(t, -t); - this._bounds.isValid() && (this._pxBounds = new o.Bounds(this._map.latLngToLayerPoint(this._bounds.getSouthWest())._subtract(e), this._map.latLngToLayerPoint(this._bounds.getNorthEast())._add(e))) }, _projectLatlngs: function(t, e) { var i, n, s = t[0] instanceof o.LatLng, - r = t.length; if (s) { for (n = [], i = 0; r > i; i++) n[i] = this._map.latLngToLayerPoint(t[i]); - e.push(n) } else - for (i = 0; r > i; i++) this._projectLatlngs(t[i], e) }, _clipPoints: function() { var t = this._renderer._bounds; if (this._parts = [], this._pxBounds && this._pxBounds.intersects(t)) { if (this.options.noClip) return void(this._parts = this._rings); var e, i, n, s, r, a, h, l = this._parts; for (e = 0, n = 0, s = this._rings.length; s > e; e++) - for (h = this._rings[e], i = 0, r = h.length; r - 1 > i; i++) a = o.LineUtil.clipSegment(h[i], h[i + 1], t, i, !0), a && (l[n] = l[n] || [], l[n].push(a[0]), (a[1] !== h[i + 1] || i === r - 2) && (l[n].push(a[1]), n++)) } }, _simplifyPoints: function() { for (var t = this._parts, e = this.options.smoothFactor, i = 0, n = t.length; n > i; i++) t[i] = o.LineUtil.simplify(t[i], e) }, _update: function() { this._map && (this._clipPoints(), this._simplifyPoints(), this._updatePath()) }, _updatePath: function() { this._renderer._updatePoly(this) } }), o.polyline = function(t, e) { return new o.Polyline(t, e) }, o.Polyline._flat = function(t) { return !o.Util.isArray(t[0]) || "object" != typeof t[0][0] && "undefined" != typeof t[0][0] }, o.PolyUtil = {}, o.PolyUtil.clipPolygon = function(t, e, i) { var n, s, r, a, h, l, u, c, d, _ = [1, 4, 2, 8], - m = o.LineUtil; for (s = 0, u = t.length; u > s; s++) t[s]._code = m._getBitCode(t[s], e); for (a = 0; 4 > a; a++) { for (c = _[a], n = [], s = 0, u = t.length, r = u - 1; u > s; r = s++) h = t[s], l = t[r], h._code & c ? l._code & c || (d = m._getEdgeIntersection(l, h, c, e, i), d._code = m._getBitCode(d, e), n.push(d)) : (l._code & c && (d = m._getEdgeIntersection(l, h, c, e, i), d._code = m._getBitCode(d, e), n.push(d)), n.push(h)); - t = n } return t }, o.Polygon = o.Polyline.extend({ options: { fill: !0 }, isEmpty: function() { return !this._latlngs.length || !this._latlngs[0].length }, getCenter: function() { var t, e, i, n, o, s, r, a, h, l = this._rings[0], - u = l.length; if (!u) return null; for (s = r = a = 0, t = 0, e = u - 1; u > t; e = t++) i = l[t], n = l[e], o = i.y * n.x - n.y * i.x, r += (i.x + n.x) * o, a += (i.y + n.y) * o, s += 3 * o; return h = 0 === s ? l[0] : [r / s, a / s], this._map.layerPointToLatLng(h) }, _convertLatLngs: function(t) { var e = o.Polyline.prototype._convertLatLngs.call(this, t), - i = e.length; return i >= 2 && e[0] instanceof o.LatLng && e[0].equals(e[i - 1]) && e.pop(), e }, _setLatLngs: function(t) { o.Polyline.prototype._setLatLngs.call(this, t), o.Polyline._flat(this._latlngs) && (this._latlngs = [this._latlngs]) }, _defaultShape: function() { return o.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0] }, _clipPoints: function() { var t = this._renderer._bounds, - e = this.options.weight, - i = new o.Point(e, e); if (t = new o.Bounds(t.min.subtract(i), t.max.add(i)), this._parts = [], this._pxBounds && this._pxBounds.intersects(t)) { if (this.options.noClip) return void(this._parts = this._rings); for (var n, s = 0, r = this._rings.length; r > s; s++) n = o.PolyUtil.clipPolygon(this._rings[s], t, !0), n.length && this._parts.push(n) } }, _updatePath: function() { this._renderer._updatePoly(this, !0) } }), o.polygon = function(t, e) { return new o.Polygon(t, e) }, o.Rectangle = o.Polygon.extend({ initialize: function(t, e) { o.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(t), e) }, setBounds: function(t) { return this.setLatLngs(this._boundsToLatLngs(t)) }, _boundsToLatLngs: function(t) { return t = o.latLngBounds(t), [t.getSouthWest(), t.getNorthWest(), t.getNorthEast(), t.getSouthEast()] } }), o.rectangle = function(t, e) { return new o.Rectangle(t, e) }, o.CircleMarker = o.Path.extend({ options: { fill: !0, radius: 10 }, initialize: function(t, e) { o.setOptions(this, e), this._latlng = o.latLng(t), this._radius = this.options.radius }, setLatLng: function(t) { return this._latlng = o.latLng(t), this.redraw(), this.fire("move", { latlng: this._latlng }) }, getLatLng: function() { return this._latlng }, setRadius: function(t) { return this.options.radius = this._radius = t, this.redraw() }, getRadius: function() { return this._radius }, setStyle: function(t) { var e = t && t.radius || this._radius; return o.Path.prototype.setStyle.call(this, t), this.setRadius(e), this }, _project: function() { this._point = this._map.latLngToLayerPoint(this._latlng), this._updateBounds() }, _updateBounds: function() { var t = this._radius, - e = this._radiusY || t, - i = this._clickTolerance(), - n = [t + i, e + i]; - this._pxBounds = new o.Bounds(this._point.subtract(n), this._point.add(n)) }, _update: function() { this._map && this._updatePath() }, _updatePath: function() { this._renderer._updateCircle(this) }, _empty: function() { return this._radius && !this._renderer._bounds.intersects(this._pxBounds) } }), o.circleMarker = function(t, e) { return new o.CircleMarker(t, e) }, o.Circle = o.CircleMarker.extend({ initialize: function(t, e) { o.setOptions(this, e), this._latlng = o.latLng(t), this._mRadius = this.options.radius }, setRadius: function(t) { return this._mRadius = t, this.redraw() }, getRadius: function() { return this._mRadius }, getBounds: function() { var t = [this._radius, this._radiusY || this._radius]; return new o.LatLngBounds(this._map.layerPointToLatLng(this._point.subtract(t)), this._map.layerPointToLatLng(this._point.add(t))) }, setStyle: o.Path.prototype.setStyle, _project: function() { var t = this._latlng.lng, - e = this._latlng.lat, - i = this._map, - n = i.options.crs; if (n.distance === o.CRS.Earth.distance) { var s = Math.PI / 180, - r = this._mRadius / o.CRS.Earth.R / s, - a = i.project([e + r, t]), - h = i.project([e - r, t]), - l = a.add(h).divideBy(2), - u = i.unproject(l).lat, - c = Math.acos((Math.cos(r * s) - Math.sin(e * s) * Math.sin(u * s)) / (Math.cos(e * s) * Math.cos(u * s))) / s; - this._point = l.subtract(i.getPixelOrigin()), this._radius = isNaN(c) ? 0 : Math.max(Math.round(l.x - i.project([u, t - c]).x), 1), this._radiusY = Math.max(Math.round(l.y - a.y), 1) } else { var d = n.unproject(n.project(this._latlng).subtract([this._mRadius, 0])); - this._point = i.latLngToLayerPoint(this._latlng), this._radius = this._point.x - i.latLngToLayerPoint(d).x } - this._updateBounds() } }), o.circle = function(t, e, i) { return "number" == typeof e && (e = o.extend({}, i, { radius: e })), new o.Circle(t, e) }, o.SVG = o.Renderer.extend({ _initContainer: function() { this._container = o.SVG.create("svg"), this._container.setAttribute("pointer-events", "none"), this._rootGroup = o.SVG.create("g"), this._container.appendChild(this._rootGroup) }, _update: function() { if (!this._map._animatingZoom || !this._bounds) { o.Renderer.prototype._update.call(this); var t = this._bounds, - e = t.getSize(), - i = this._container; - this._svgSize && this._svgSize.equals(e) || (this._svgSize = e, i.setAttribute("width", e.x), i.setAttribute("height", e.y)), o.DomUtil.setPosition(i, t.min), i.setAttribute("viewBox", [t.min.x, t.min.y, e.x, e.y].join(" ")) } }, _initPath: function(t) { var e = t._path = o.SVG.create("path"); - t.options.className && o.DomUtil.addClass(e, t.options.className), t.options.interactive && o.DomUtil.addClass(e, "leaflet-interactive"), this._updateStyle(t) }, _addPath: function(t) { this._rootGroup.appendChild(t._path), t.addInteractiveTarget(t._path) }, _removePath: function(t) { o.DomUtil.remove(t._path), t.removeInteractiveTarget(t._path) }, _updatePath: function(t) { t._project(), t._update() }, _updateStyle: function(t) { var e = t._path, - i = t.options; - e && (i.stroke ? (e.setAttribute("stroke", i.color), e.setAttribute("stroke-opacity", i.opacity), e.setAttribute("stroke-width", i.weight), e.setAttribute("stroke-linecap", i.lineCap), e.setAttribute("stroke-linejoin", i.lineJoin), i.dashArray ? e.setAttribute("stroke-dasharray", i.dashArray) : e.removeAttribute("stroke-dasharray"), i.dashOffset ? e.setAttribute("stroke-dashoffset", i.dashOffset) : e.removeAttribute("stroke-dashoffset")) : e.setAttribute("stroke", "none"), i.fill ? (e.setAttribute("fill", i.fillColor || i.color), e.setAttribute("fill-opacity", i.fillOpacity), e.setAttribute("fill-rule", i.fillRule || "evenodd")) : e.setAttribute("fill", "none"), e.setAttribute("pointer-events", i.pointerEvents || (i.interactive ? "visiblePainted" : "none"))) }, _updatePoly: function(t, e) { this._setPath(t, o.SVG.pointsToPath(t._parts, e)) }, _updateCircle: function(t) { var e = t._point, - i = t._radius, - n = t._radiusY || i, - o = "a" + i + "," + n + " 0 1,0 ", - s = t._empty() ? "M0 0" : "M" + (e.x - i) + "," + e.y + o + 2 * i + ",0 " + o + 2 * -i + ",0 "; - this._setPath(t, s) }, _setPath: function(t, e) { t._path.setAttribute("d", e) }, _bringToFront: function(t) { o.DomUtil.toFront(t._path) }, _bringToBack: function(t) { o.DomUtil.toBack(t._path) } }), o.extend(o.SVG, { create: function(t) { return e.createElementNS("http://www.w3.org/2000/svg", t) }, pointsToPath: function(t, e) { var i, n, s, r, a, h, l = ""; for (i = 0, s = t.length; s > i; i++) { for (a = t[i], n = 0, r = a.length; r > n; n++) h = a[n], l += (n ? "L" : "M") + h.x + " " + h.y; - l += e ? o.Browser.svg ? "z" : "x" : "" } return l || "M0 0" } }), o.Browser.svg = !(!e.createElementNS || !o.SVG.create("svg").createSVGRect), o.svg = function(t) { return o.Browser.svg || o.Browser.vml ? new o.SVG(t) : null }, o.Browser.vml = !o.Browser.svg && function() { try { var t = e.createElement("div"); - t.innerHTML = ''; var i = t.firstChild; return i.style.behavior = "url(#default#VML)", i && "object" == typeof i.adj } catch (n) { return !1 } }(), o.SVG.include(o.Browser.vml ? { _initContainer: function() { this._container = o.DomUtil.create("div", "leaflet-vml-container") }, _update: function() { this._map._animatingZoom || o.Renderer.prototype._update.call(this) }, _initPath: function(t) { var e = t._container = o.SVG.create("shape"); - o.DomUtil.addClass(e, "leaflet-vml-shape " + (this.options.className || "")), e.coordsize = "1 1", t._path = o.SVG.create("path"), e.appendChild(t._path), this._updateStyle(t) }, _addPath: function(t) { var e = t._container; - this._container.appendChild(e), t.options.interactive && t.addInteractiveTarget(e) }, _removePath: function(t) { var e = t._container; - o.DomUtil.remove(e), t.removeInteractiveTarget(e) }, _updateStyle: function(t) { var e = t._stroke, - i = t._fill, - n = t.options, - s = t._container; - s.stroked = !!n.stroke, s.filled = !!n.fill, n.stroke ? (e || (e = t._stroke = o.SVG.create("stroke")), s.appendChild(e), e.weight = n.weight + "px", e.color = n.color, e.opacity = n.opacity, n.dashArray ? e.dashStyle = o.Util.isArray(n.dashArray) ? n.dashArray.join(" ") : n.dashArray.replace(/( *, *)/g, " ") : e.dashStyle = "", e.endcap = n.lineCap.replace("butt", "flat"), e.joinstyle = n.lineJoin) : e && (s.removeChild(e), t._stroke = null), n.fill ? (i || (i = t._fill = o.SVG.create("fill")), s.appendChild(i), i.color = n.fillColor || n.color, i.opacity = n.fillOpacity) : i && (s.removeChild(i), t._fill = null) }, _updateCircle: function(t) { var e = t._point.round(), - i = Math.round(t._radius), - n = Math.round(t._radiusY || i); - this._setPath(t, t._empty() ? "M0 0" : "AL " + e.x + "," + e.y + " " + i + "," + n + " 0,23592600") }, _setPath: function(t, e) { t._path.v = e }, _bringToFront: function(t) { o.DomUtil.toFront(t._container) }, _bringToBack: function(t) { o.DomUtil.toBack(t._container) } } : {}), o.Browser.vml && (o.SVG.create = function() { try { return e.namespaces.add("lvml", "urn:schemas-microsoft-com:vml"), - function(t) { return e.createElement("') } } catch (t) { return function(t) { return e.createElement("<" + t + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">') } } }()), o.Canvas = o.Renderer.extend({ onAdd: function() { o.Renderer.prototype.onAdd.call(this), this._layers = this._layers || {}, this._draw() }, _initContainer: function() { var t = this._container = e.createElement("canvas"); - o.DomEvent.on(t, "mousemove", o.Util.throttle(this._onMouseMove, 32, this), this).on(t, "click dblclick mousedown mouseup contextmenu", this._onClick, this).on(t, "mouseout", this._handleMouseOut, this), this._ctx = t.getContext("2d") }, _update: function() { if (!this._map._animatingZoom || !this._bounds) { this._drawnLayers = {}, o.Renderer.prototype._update.call(this); var t = this._bounds, - e = this._container, - i = t.getSize(), - n = o.Browser.retina ? 2 : 1; - o.DomUtil.setPosition(e, t.min), e.width = n * i.x, e.height = n * i.y, e.style.width = i.x + "px", e.style.height = i.y + "px", o.Browser.retina && this._ctx.scale(2, 2), this._ctx.translate(-t.min.x, -t.min.y) } }, _initPath: function(t) { this._layers[o.stamp(t)] = t }, _addPath: o.Util.falseFn, _removePath: function(t) { t._removed = !0, this._requestRedraw(t) }, _updatePath: function(t) { this._redrawBounds = t._pxBounds, this._draw(!0), t._project(), t._update(), this._draw(), this._redrawBounds = null }, _updateStyle: function(t) { this._requestRedraw(t) }, _requestRedraw: function(t) { if (this._map) { var e = (t.options.weight || 0) + 1; - this._redrawBounds = this._redrawBounds || new o.Bounds, this._redrawBounds.extend(t._pxBounds.min.subtract([e, e])), this._redrawBounds.extend(t._pxBounds.max.add([e, e])), this._redrawRequest = this._redrawRequest || o.Util.requestAnimFrame(this._redraw, this) } }, _redraw: function() { this._redrawRequest = null, this._draw(!0), this._draw(), this._redrawBounds = null }, _draw: function(t) { this._clear = t; var e, i = this._redrawBounds; - this._ctx.save(), i && (this._ctx.beginPath(), this._ctx.rect(i.min.x, i.min.y, i.max.x - i.min.x, i.max.y - i.min.y), this._ctx.clip()); for (var n in this._layers) e = this._layers[n], (!i || e._pxBounds.intersects(i)) && e._updatePath(), t && e._removed && (delete e._removed, delete this._layers[n]); - this._ctx.restore() }, _updatePoly: function(t, e) { var i, n, o, s, r = t._parts, - a = r.length, - h = this._ctx; if (a) { for (this._drawnLayers[t._leaflet_id] = t, h.beginPath(), i = 0; a > i; i++) { for (n = 0, o = r[i].length; o > n; n++) s = r[i][n], h[n ? "lineTo" : "moveTo"](s.x, s.y); - e && h.closePath() } - this._fillStroke(h, t) } }, _updateCircle: function(t) { if (!t._empty()) { var e = t._point, - i = this._ctx, - n = t._radius, - o = (t._radiusY || n) / n; - 1 !== o && (i.save(), i.scale(1, o)), i.beginPath(), i.arc(e.x, e.y / o, n, 0, 2 * Math.PI, !1), 1 !== o && i.restore(), this._fillStroke(i, t) } }, _fillStroke: function(t, e) { var i = this._clear, - n = e.options; - t.globalCompositeOperation = i ? "destination-out" : "source-over", n.fill && (t.globalAlpha = i ? 1 : n.fillOpacity, t.fillStyle = n.fillColor || n.color, t.fill(n.fillRule || "evenodd")), n.stroke && 0 !== n.weight && (t.globalAlpha = i ? 1 : n.opacity, e._prevWeight = t.lineWidth = i ? e._prevWeight + 1 : n.weight, t.strokeStyle = n.color, t.lineCap = n.lineCap, t.lineJoin = n.lineJoin, t.stroke()) }, _onClick: function(t) { var e = this._map.mouseEventToLayerPoint(t), - i = []; for (var n in this._layers) this._layers[n]._containsPoint(e) && (o.DomEvent._fakeStop(t), i.push(this._layers[n])); - i.length && this._fireEvent(i, t) }, _onMouseMove: function(t) { if (this._map && !this._map.dragging._draggable._moving && !this._map._animatingZoom) { var e = this._map.mouseEventToLayerPoint(t); - this._handleMouseOut(t, e), this._handleMouseHover(t, e) } }, _handleMouseOut: function(t, e) { var i = this._hoveredLayer;!i || "mouseout" !== t.type && i._containsPoint(e) || (o.DomUtil.removeClass(this._container, "leaflet-interactive"), this._fireEvent([i], t, "mouseout"), this._hoveredLayer = null) }, _handleMouseHover: function(t, e) { var i, n; if (!this._hoveredLayer) - for (i in this._drawnLayers) - if (n = this._drawnLayers[i], n.options.interactive && n._containsPoint(e)) { o.DomUtil.addClass(this._container, "leaflet-interactive"), this._fireEvent([n], t, "mouseover"), this._hoveredLayer = n; break } - this._hoveredLayer && this._fireEvent([this._hoveredLayer], t) }, _fireEvent: function(t, e, i) { this._map._fireDOMEvent(e, i || e.type, t) }, _bringToFront: o.Util.falseFn, _bringToBack: o.Util.falseFn }), o.Browser.canvas = function() { return !!e.createElement("canvas").getContext }(), o.canvas = function(t) { return o.Browser.canvas ? new o.Canvas(t) : null }, o.Polyline.prototype._containsPoint = function(t, e) { var i, n, s, r, a, h, l = this._clickTolerance(); if (!this._pxBounds.contains(t)) return !1; for (i = 0, r = this._parts.length; r > i; i++) - for (h = this._parts[i], n = 0, a = h.length, s = a - 1; a > n; s = n++) - if ((e || 0 !== n) && o.LineUtil.pointToSegmentDistance(t, h[s], h[n]) <= l) return !0; - return !1 }, o.Polygon.prototype._containsPoint = function(t) { var e, i, n, s, r, a, h, l, u = !1; if (!this._pxBounds.contains(t)) return !1; for (s = 0, h = this._parts.length; h > s; s++) - for (e = this._parts[s], r = 0, l = e.length, a = l - 1; l > r; a = r++) i = e[r], n = e[a], i.y > t.y != n.y > t.y && t.x < (n.x - i.x) * (t.y - i.y) / (n.y - i.y) + i.x && (u = !u); return u || o.Polyline.prototype._containsPoint.call(this, t, !0) }, o.CircleMarker.prototype._containsPoint = function(t) { return t.distanceTo(this._point) <= this._radius + this._clickTolerance() }, o.GeoJSON = o.FeatureGroup.extend({ initialize: function(t, e) { o.setOptions(this, e), this._layers = {}, t && this.addData(t) }, addData: function(t) { var e, i, n, s = o.Util.isArray(t) ? t : t.features; if (s) { for (e = 0, i = s.length; i > e; e++) n = s[e], (n.geometries || n.geometry || n.features || n.coordinates) && this.addData(n); return this } var r = this.options; if (r.filter && !r.filter(t)) return this; var a = o.GeoJSON.geometryToLayer(t, r); return a ? (a.feature = o.GeoJSON.asFeature(t), a.defaultOptions = a.options, this.resetStyle(a), r.onEachFeature && r.onEachFeature(t, a), this.addLayer(a)) : this }, resetStyle: function(t) { return t.options = t.defaultOptions, this._setLayerStyle(t, this.options.style), this }, setStyle: function(t) { return this.eachLayer(function(e) { this._setLayerStyle(e, t) }, this) }, _setLayerStyle: function(t, e) { "function" == typeof e && (e = e(t.feature)), t.setStyle && t.setStyle(e) } }), o.extend(o.GeoJSON, { geometryToLayer: function(t, e) { var i, n, s, r, a = "Feature" === t.type ? t.geometry : t, - h = a ? a.coordinates : null, - l = [], - u = e && e.pointToLayer, - c = e && e.coordsToLatLng || this.coordsToLatLng; if (!h && !a) return null; switch (a.type) { - case "Point": - return i = c(h), u ? u(t, i) : new o.Marker(i); - case "MultiPoint": - for (s = 0, r = h.length; r > s; s++) i = c(h[s]), l.push(u ? u(t, i) : new o.Marker(i)); return new o.FeatureGroup(l); - case "LineString": - case "MultiLineString": - return n = this.coordsToLatLngs(h, "LineString" === a.type ? 0 : 1, c), new o.Polyline(n, e); - case "Polygon": - case "MultiPolygon": - return n = this.coordsToLatLngs(h, "Polygon" === a.type ? 1 : 2, c), new o.Polygon(n, e); - case "GeometryCollection": - for (s = 0, r = a.geometries.length; r > s; s++) { var d = this.geometryToLayer({ geometry: a.geometries[s], type: "Feature", properties: t.properties }, e); - d && l.push(d) } return new o.FeatureGroup(l); - default: - throw new Error("Invalid GeoJSON object.") } }, coordsToLatLng: function(t) { return new o.LatLng(t[1], t[0], t[2]) }, coordsToLatLngs: function(t, e, i) { for (var n, o = [], s = 0, r = t.length; r > s; s++) n = e ? this.coordsToLatLngs(t[s], e - 1, i) : (i || this.coordsToLatLng)(t[s]), o.push(n); return o }, latLngToCoords: function(t) { return t.alt !== i ? [t.lng, t.lat, t.alt] : [t.lng, t.lat] }, latLngsToCoords: function(t, e, i) { for (var n = [], s = 0, r = t.length; r > s; s++) n.push(e ? o.GeoJSON.latLngsToCoords(t[s], e - 1, i) : o.GeoJSON.latLngToCoords(t[s])); return !e && i && n.push(n[0]), n }, getFeature: function(t, e) { return t.feature ? o.extend({}, t.feature, { geometry: e }) : o.GeoJSON.asFeature(e) }, asFeature: function(t) { return "Feature" === t.type ? t : { type: "Feature", properties: {}, geometry: t } } }); - var r = { toGeoJSON: function() { return o.GeoJSON.getFeature(this, { type: "Point", coordinates: o.GeoJSON.latLngToCoords(this.getLatLng()) }) } }; - o.Marker.include(r), o.Circle.include(r), o.CircleMarker.include(r), o.Polyline.prototype.toGeoJSON = function() { var t = !o.Polyline._flat(this._latlngs), - e = o.GeoJSON.latLngsToCoords(this._latlngs, t ? 1 : 0); return o.GeoJSON.getFeature(this, { type: (t ? "Multi" : "") + "LineString", coordinates: e }) }, o.Polygon.prototype.toGeoJSON = function() { var t = !o.Polyline._flat(this._latlngs), - e = t && !o.Polyline._flat(this._latlngs[0]), - i = o.GeoJSON.latLngsToCoords(this._latlngs, e ? 2 : t ? 1 : 0, !0); return t || (i = [i]), o.GeoJSON.getFeature(this, { type: (e ? "Multi" : "") + "Polygon", coordinates: i }) }, o.LayerGroup.include({ toMultiPoint: function() { var t = []; return this.eachLayer(function(e) { t.push(e.toGeoJSON().geometry.coordinates) }), o.GeoJSON.getFeature(this, { type: "MultiPoint", coordinates: t }) }, toGeoJSON: function() { var t = this.feature && this.feature.geometry && this.feature.geometry.type; if ("MultiPoint" === t) return this.toMultiPoint(); var e = "GeometryCollection" === t, - i = []; return this.eachLayer(function(t) { if (t.toGeoJSON) { var n = t.toGeoJSON(); - i.push(e ? n.geometry : o.GeoJSON.asFeature(n)) } }), e ? o.GeoJSON.getFeature(this, { geometries: i, type: "GeometryCollection" }) : { type: "FeatureCollection", features: i } } }), o.geoJson = function(t, e) { return new o.GeoJSON(t, e) }; - var a = "_leaflet_events"; - o.DomEvent = { on: function(t, e, i, n) { if ("object" == typeof e) - for (var s in e) this._on(t, s, e[s], i); - else { e = o.Util.splitWords(e); for (var r = 0, a = e.length; a > r; r++) this._on(t, e[r], i, n) } return this }, off: function(t, e, i, n) { if ("object" == typeof e) - for (var s in e) this._off(t, s, e[s], i); - else { e = o.Util.splitWords(e); for (var r = 0, a = e.length; a > r; r++) this._off(t, e[r], i, n) } return this }, _on: function(e, i, n, s) { var r = i + o.stamp(n) + (s ? "_" + o.stamp(s) : ""); if (e[a] && e[a][r]) return this; var h = function(i) { return n.call(s || e, i || t.event) }, - l = h; return o.Browser.pointer && 0 === i.indexOf("touch") ? this.addPointerListener(e, i, h, r) : o.Browser.touch && "dblclick" === i && this.addDoubleTapListener ? this.addDoubleTapListener(e, h, r) : "addEventListener" in e ? "mousewheel" === i ? (e.addEventListener("DOMMouseScroll", h, !1), e.addEventListener(i, h, !1)) : "mouseenter" === i || "mouseleave" === i ? (h = function(i) { i = i || t.event, o.DomEvent._isExternalTarget(e, i) && l(i) }, e.addEventListener("mouseenter" === i ? "mouseover" : "mouseout", h, !1)) : ("click" === i && o.Browser.android && (h = function(t) { return o.DomEvent._filterClick(t, l) }), e.addEventListener(i, h, !1)) : "attachEvent" in e && e.attachEvent("on" + i, h), e[a] = e[a] || {}, e[a][r] = h, this }, _off: function(t, e, i, n) { var s = e + o.stamp(i) + (n ? "_" + o.stamp(n) : ""), - r = t[a] && t[a][s]; return r ? (o.Browser.pointer && 0 === e.indexOf("touch") ? this.removePointerListener(t, e, s) : o.Browser.touch && "dblclick" === e && this.removeDoubleTapListener ? this.removeDoubleTapListener(t, s) : "removeEventListener" in t ? "mousewheel" === e ? (t.removeEventListener("DOMMouseScroll", r, !1), t.removeEventListener(e, r, !1)) : t.removeEventListener("mouseenter" === e ? "mouseover" : "mouseleave" === e ? "mouseout" : e, r, !1) : "detachEvent" in t && t.detachEvent("on" + e, r), t[a][s] = null, this) : this }, stopPropagation: function(t) { return t.stopPropagation ? t.stopPropagation() : t.originalEvent ? t.originalEvent._stopped = !0 : t.cancelBubble = !0, o.DomEvent._skipped(t), this }, disableScrollPropagation: function(t) { return o.DomEvent.on(t, "mousewheel MozMousePixelScroll", o.DomEvent.stopPropagation) }, disableClickPropagation: function(t) { var e = o.DomEvent.stopPropagation; return o.DomEvent.on(t, o.Draggable.START.join(" "), e), o.DomEvent.on(t, { click: o.DomEvent._fakeStop, dblclick: e }) }, preventDefault: function(t) { return t.preventDefault ? t.preventDefault() : t.returnValue = !1, this }, stop: function(t) { return o.DomEvent.preventDefault(t).stopPropagation(t) }, getMousePosition: function(t, e) { if (!e) return new o.Point(t.clientX, t.clientY); var i = e.getBoundingClientRect(); return new o.Point(t.clientX - i.left - e.clientLeft, t.clientY - i.top - e.clientTop) }, getWheelDelta: function(t) { var e = 0; return t.wheelDelta && (e = t.wheelDelta / 120), t.detail && (e = -t.detail / 3), e }, _skipEvents: {}, _fakeStop: function(t) { o.DomEvent._skipEvents[t.type] = !0 }, _skipped: function(t) { var e = this._skipEvents[t.type]; return this._skipEvents[t.type] = !1, e }, _isExternalTarget: function(t, e) { var i = e.relatedTarget; if (!i) return !0; try { for (; i && i !== t;) i = i.parentNode } catch (n) { return !1 } return i !== t }, _filterClick: function(t, e) { var i = t.timeStamp || t.originalEvent.timeStamp, - n = o.DomEvent._lastClick && i - o.DomEvent._lastClick; return n && n > 100 && 500 > n || t.target._simulatedClick && !t._simulated ? void o.DomEvent.stop(t) : (o.DomEvent._lastClick = i, void e(t)) } }, o.DomEvent.addListener = o.DomEvent.on, o.DomEvent.removeListener = o.DomEvent.off, o.Draggable = o.Evented.extend({ - statics: { START: o.Browser.touch ? ["touchstart", "mousedown"] : ["mousedown"], END: { mousedown: "mouseup", touchstart: "touchend", pointerdown: "touchend", MSPointerDown: "touchend" }, MOVE: { mousedown: "mousemove", touchstart: "touchmove", pointerdown: "touchmove", MSPointerDown: "touchmove" } }, - initialize: function(t, e, i) { this._element = t, this._dragStartTarget = e || t, this._preventOutline = i }, - enable: function() { this._enabled || (o.DomEvent.on(this._dragStartTarget, o.Draggable.START.join(" "), this._onDown, this), this._enabled = !0) }, - disable: function() { this._enabled && (o.DomEvent.off(this._dragStartTarget, o.Draggable.START.join(" "), this._onDown, this), this._enabled = !1, this._moved = !1) }, - _onDown: function(t) { if (this._moved = !1, !o.DomUtil.hasClass(this._element, "leaflet-zoom-anim") && !(o.Draggable._dragging || t.shiftKey || 1 !== t.which && 1 !== t.button && !t.touches) && this._enabled && (o.Draggable._dragging = !0, this._preventOutline && o.DomUtil.preventOutline(this._element), o.DomUtil.disableImageDrag(), o.DomUtil.disableTextSelection(), !this._moving)) { this.fire("down"); var i = t.touches ? t.touches[0] : t; - this._startPoint = new o.Point(i.clientX, i.clientY), this._startPos = this._newPos = o.DomUtil.getPosition(this._element), o.DomEvent.on(e, o.Draggable.MOVE[t.type], this._onMove, this).on(e, o.Draggable.END[t.type], this._onUp, this) } }, - _onMove: function(t) { - if (t.touches && t.touches.length > 1) return void(this._moved = !0); - var i = t.touches && 1 === t.touches.length ? t.touches[0] : t, - n = new o.Point(i.clientX, i.clientY), - s = n.subtract(this._startPoint); - (s.x || s.y) && (o.Browser.touch && Math.abs(s.x) + Math.abs(s.y) < 3 || (o.DomEvent.preventDefault(t), this._moved || (this.fire("dragstart"), this._moved = !0, this._startPos = o.DomUtil.getPosition(this._element).subtract(s), o.DomUtil.addClass(e.body, "leaflet-dragging"), this._lastTarget = t.target || t.srcElement, o.DomUtil.addClass(this._lastTarget, "leaflet-drag-target")), - this._newPos = this._startPos.add(s), this._moving = !0, o.Util.cancelAnimFrame(this._animRequest), this._lastEvent = t, this._animRequest = o.Util.requestAnimFrame(this._updatePosition, this, !0))) - }, - _updatePosition: function() { var t = { originalEvent: this._lastEvent }; - this.fire("predrag", t), o.DomUtil.setPosition(this._element, this._newPos), this.fire("drag", t) }, - _onUp: function() { o.DomUtil.removeClass(e.body, "leaflet-dragging"), this._lastTarget && (o.DomUtil.removeClass(this._lastTarget, "leaflet-drag-target"), this._lastTarget = null); for (var t in o.Draggable.MOVE) o.DomEvent.off(e, o.Draggable.MOVE[t], this._onMove, this).off(e, o.Draggable.END[t], this._onUp, this); - o.DomUtil.enableImageDrag(), o.DomUtil.enableTextSelection(), this._moved && this._moving && (o.Util.cancelAnimFrame(this._animRequest), this.fire("dragend", { distance: this._newPos.distanceTo(this._startPos) })), this._moving = !1, o.Draggable._dragging = !1 } - }), o.Handler = o.Class.extend({ initialize: function(t) { this._map = t }, enable: function() { this._enabled || (this._enabled = !0, this.addHooks()) }, disable: function() { this._enabled && (this._enabled = !1, this.removeHooks()) }, enabled: function() { return !!this._enabled } }), o.Map.mergeOptions({ dragging: !0, inertia: !o.Browser.android23, inertiaDeceleration: 3400, inertiaMaxSpeed: 1 / 0, easeLinearity: .2, worldCopyJump: !1 }), o.Map.Drag = o.Handler.extend({ addHooks: function() { if (!this._draggable) { var t = this._map; - this._draggable = new o.Draggable(t._mapPane, t._container), this._draggable.on({ down: this._onDown, dragstart: this._onDragStart, drag: this._onDrag, dragend: this._onDragEnd }, this), this._draggable.on("predrag", this._onPreDragLimit, this), t.options.worldCopyJump && (this._draggable.on("predrag", this._onPreDragWrap, this), t.on("zoomend", this._onZoomEnd, this), t.whenReady(this._onZoomEnd, this)) } - o.DomUtil.addClass(this._map._container, "leaflet-grab"), this._draggable.enable() }, removeHooks: function() { o.DomUtil.removeClass(this._map._container, "leaflet-grab"), this._draggable.disable() }, moved: function() { return this._draggable && this._draggable._moved }, _onDown: function() { this._map.stop() }, _onDragStart: function() { var t = this._map; if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { var e = o.latLngBounds(this._map.options.maxBounds); - this._offsetLimit = o.bounds(this._map.latLngToContainerPoint(e.getNorthWest()).multiplyBy(-1), this._map.latLngToContainerPoint(e.getSouthEast()).multiplyBy(-1).add(this._map.getSize())), this._viscosity = Math.min(1, Math.max(0, this._map.options.maxBoundsViscosity)) } else this._offsetLimit = null; - t.fire("movestart").fire("dragstart"), t.options.inertia && (this._positions = [], this._times = []) }, _onDrag: function(t) { if (this._map.options.inertia) { var e = this._lastTime = +new Date, - i = this._lastPos = this._draggable._absPos || this._draggable._newPos; - this._positions.push(i), this._times.push(e), e - this._times[0] > 50 && (this._positions.shift(), this._times.shift()) } - this._map.fire("move", t).fire("drag", t) }, _onZoomEnd: function() { var t = this._map.getSize().divideBy(2), - e = this._map.latLngToLayerPoint([0, 0]); - this._initialWorldOffset = e.subtract(t).x, this._worldWidth = this._map.getPixelWorldBounds().getSize().x }, _viscousLimit: function(t, e) { return t - (t - e) * this._viscosity }, _onPreDragLimit: function() { if (this._viscosity && this._offsetLimit) { var t = this._draggable._newPos.subtract(this._draggable._startPos), - e = this._offsetLimit; - t.x < e.min.x && (t.x = this._viscousLimit(t.x, e.min.x)), t.y < e.min.y && (t.y = this._viscousLimit(t.y, e.min.y)), t.x > e.max.x && (t.x = this._viscousLimit(t.x, e.max.x)), t.y > e.max.y && (t.y = this._viscousLimit(t.y, e.max.y)), this._draggable._newPos = this._draggable._startPos.add(t) } }, _onPreDragWrap: function() { var t = this._worldWidth, - e = Math.round(t / 2), - i = this._initialWorldOffset, - n = this._draggable._newPos.x, - o = (n - e + i) % t + e - i, - s = (n + e + i) % t - e - i, - r = Math.abs(o + i) < Math.abs(s + i) ? o : s; - this._draggable._absPos = this._draggable._newPos.clone(), this._draggable._newPos.x = r }, _onDragEnd: function(t) { var e = this._map, - i = e.options, - n = !i.inertia || this._times.length < 2; if (e.fire("dragend", t), n) e.fire("moveend"); - else { var s = this._lastPos.subtract(this._positions[0]), - r = (this._lastTime - this._times[0]) / 1e3, - a = i.easeLinearity, - h = s.multiplyBy(a / r), - l = h.distanceTo([0, 0]), - u = Math.min(i.inertiaMaxSpeed, l), - c = h.multiplyBy(u / l), - d = u / (i.inertiaDeceleration * a), - _ = c.multiplyBy(-d / 2).round(); - _.x || _.y ? (_ = e._limitOffset(_, e.options.maxBounds), o.Util.requestAnimFrame(function() { e.panBy(_, { duration: d, easeLinearity: a, noMoveStart: !0, animate: !0 }) })) : e.fire("moveend") } } }), o.Map.addInitHook("addHandler", "dragging", o.Map.Drag), o.Map.mergeOptions({ doubleClickZoom: !0 }), o.Map.DoubleClickZoom = o.Handler.extend({ addHooks: function() { this._map.on("dblclick", this._onDoubleClick, this) }, removeHooks: function() { this._map.off("dblclick", this._onDoubleClick, this) }, _onDoubleClick: function(t) { var e = this._map, - i = e.getZoom(), - n = t.originalEvent.shiftKey ? Math.ceil(i) - 1 : Math.floor(i) + 1; "center" === e.options.doubleClickZoom ? e.setZoom(n) : e.setZoomAround(t.containerPoint, n) } }), o.Map.addInitHook("addHandler", "doubleClickZoom", o.Map.DoubleClickZoom), o.Map.mergeOptions({ scrollWheelZoom: !0, wheelDebounceTime: 40 }), o.Map.ScrollWheelZoom = o.Handler.extend({ addHooks: function() { o.DomEvent.on(this._map._container, { mousewheel: this._onWheelScroll, MozMousePixelScroll: o.DomEvent.preventDefault }, this), this._delta = 0 }, removeHooks: function() { o.DomEvent.off(this._map._container, { mousewheel: this._onWheelScroll, MozMousePixelScroll: o.DomEvent.preventDefault }, this) }, _onWheelScroll: function(t) { var e = o.DomEvent.getWheelDelta(t), - i = this._map.options.wheelDebounceTime; - this._delta += e, this._lastMousePos = this._map.mouseEventToContainerPoint(t), this._startTime || (this._startTime = +new Date); var n = Math.max(i - (+new Date - this._startTime), 0); - clearTimeout(this._timer), this._timer = setTimeout(o.bind(this._performZoom, this), n), o.DomEvent.stop(t) }, _performZoom: function() { var t = this._map, - e = this._delta, - i = t.getZoom(); - t.stop(), e = e > 0 ? Math.ceil(e) : Math.floor(e), e = Math.max(Math.min(e, 4), -4), e = t._limitZoom(i + e) - i, this._delta = 0, this._startTime = null, e && ("center" === t.options.scrollWheelZoom ? t.setZoom(i + e) : t.setZoomAround(this._lastMousePos, i + e)) } }), o.Map.addInitHook("addHandler", "scrollWheelZoom", o.Map.ScrollWheelZoom), o.extend(o.DomEvent, { _touchstart: o.Browser.msPointer ? "MSPointerDown" : o.Browser.pointer ? "pointerdown" : "touchstart", _touchend: o.Browser.msPointer ? "MSPointerUp" : o.Browser.pointer ? "pointerup" : "touchend", addDoubleTapListener: function(t, e, i) { - function n(t) { var e; if (e = o.Browser.pointer ? o.DomEvent._pointersCount : t.touches.length, !(e > 1)) { var i = Date.now(), - n = i - (r || i); - a = t.touches ? t.touches[0] : t, h = n > 0 && l >= n, r = i } } - - function s() { if (h && !a.cancelBubble) { if (o.Browser.pointer) { var t, i, n = {}; for (i in a) t = a[i], n[i] = t && t.bind ? t.bind(a) : t; - a = n } - a.type = "dblclick", e(a), r = null } } var r, a, h = !1, - l = 250, - u = "_leaflet_", - c = this._touchstart, - d = this._touchend; return t[u + c + i] = n, t[u + d + i] = s, t.addEventListener(c, n, !1), t.addEventListener(d, s, !1), this }, removeDoubleTapListener: function(t, e) { var i = "_leaflet_", - n = t[i + this._touchend + e]; return t.removeEventListener(this._touchstart, t[i + this._touchstart + e], !1), t.removeEventListener(this._touchend, n, !1), this } }), o.extend(o.DomEvent, { POINTER_DOWN: o.Browser.msPointer ? "MSPointerDown" : "pointerdown", POINTER_MOVE: o.Browser.msPointer ? "MSPointerMove" : "pointermove", POINTER_UP: o.Browser.msPointer ? "MSPointerUp" : "pointerup", POINTER_CANCEL: o.Browser.msPointer ? "MSPointerCancel" : "pointercancel", _pointers: {}, _pointersCount: 0, addPointerListener: function(t, e, i, n) { return "touchstart" === e ? this._addPointerStart(t, i, n) : "touchmove" === e ? this._addPointerMove(t, i, n) : "touchend" === e && this._addPointerEnd(t, i, n), this }, removePointerListener: function(t, e, i) { var n = t["_leaflet_" + e + i]; return "touchstart" === e ? t.removeEventListener(this.POINTER_DOWN, n, !1) : "touchmove" === e ? t.removeEventListener(this.POINTER_MOVE, n, !1) : "touchend" === e && (t.removeEventListener(this.POINTER_UP, n, !1), t.removeEventListener(this.POINTER_CANCEL, n, !1)), this }, _addPointerStart: function(t, i, n) { var s = o.bind(function(t) { "mouse" !== t.pointerType && t.pointerType !== t.MSPOINTER_TYPE_MOUSE && o.DomEvent.preventDefault(t), this._handlePointer(t, i) }, this); if (t["_leaflet_touchstart" + n] = s, t.addEventListener(this.POINTER_DOWN, s, !1), !this._pointerDocListener) { var r = o.bind(this._globalPointerUp, this); - e.documentElement.addEventListener(this.POINTER_DOWN, o.bind(this._globalPointerDown, this), !0), e.documentElement.addEventListener(this.POINTER_MOVE, o.bind(this._globalPointerMove, this), !0), e.documentElement.addEventListener(this.POINTER_UP, r, !0), e.documentElement.addEventListener(this.POINTER_CANCEL, r, !0), this._pointerDocListener = !0 } }, _globalPointerDown: function(t) { this._pointers[t.pointerId] = t, this._pointersCount++ }, _globalPointerMove: function(t) { this._pointers[t.pointerId] && (this._pointers[t.pointerId] = t) }, _globalPointerUp: function(t) { delete this._pointers[t.pointerId], this._pointersCount-- }, _handlePointer: function(t, e) { t.touches = []; for (var i in this._pointers) t.touches.push(this._pointers[i]); - t.changedTouches = [t], e(t) }, _addPointerMove: function(t, e, i) { var n = o.bind(function(t) { - (t.pointerType !== t.MSPOINTER_TYPE_MOUSE && "mouse" !== t.pointerType || 0 !== t.buttons) && this._handlePointer(t, e) }, this); - t["_leaflet_touchmove" + i] = n, t.addEventListener(this.POINTER_MOVE, n, !1) }, _addPointerEnd: function(t, e, i) { var n = o.bind(function(t) { this._handlePointer(t, e) }, this); - t["_leaflet_touchend" + i] = n, t.addEventListener(this.POINTER_UP, n, !1), t.addEventListener(this.POINTER_CANCEL, n, !1) } }), o.Map.mergeOptions({ touchZoom: o.Browser.touch && !o.Browser.android23, bounceAtZoomLimits: !0 }), o.Map.TouchZoom = o.Handler.extend({ addHooks: function() { o.DomEvent.on(this._map._container, "touchstart", this._onTouchStart, this) }, removeHooks: function() { o.DomEvent.off(this._map._container, "touchstart", this._onTouchStart, this) }, _onTouchStart: function(t) { var i = this._map; if (t.touches && 2 === t.touches.length && !i._animatingZoom && !this._zooming) { var n = i.mouseEventToContainerPoint(t.touches[0]), - s = i.mouseEventToContainerPoint(t.touches[1]); - this._centerPoint = i.getSize()._divideBy(2), this._startLatLng = i.containerPointToLatLng(this._centerPoint), "center" !== i.options.touchZoom && (this._pinchStartLatLng = i.containerPointToLatLng(n.add(s)._divideBy(2))), this._startDist = n.distanceTo(s), this._startZoom = i.getZoom(), this._moved = !1, this._zooming = !0, i.stop(), o.DomEvent.on(e, "touchmove", this._onTouchMove, this).on(e, "touchend", this._onTouchEnd, this), o.DomEvent.preventDefault(t) } }, _onTouchMove: function(t) { if (t.touches && 2 === t.touches.length && this._zooming) { var e = this._map, - i = e.mouseEventToContainerPoint(t.touches[0]), - n = e.mouseEventToContainerPoint(t.touches[1]), - s = i.distanceTo(n) / this._startDist; if (this._zoom = e.getScaleZoom(s, this._startZoom), "center" === e.options.touchZoom) { if (this._center = this._startLatLng, 1 === s) return } else { var r = i._add(n)._divideBy(2)._subtract(this._centerPoint); if (1 === s && 0 === r.x && 0 === r.y) return; - this._center = e.unproject(e.project(this._pinchStartLatLng).subtract(r)) } if (e.options.bounceAtZoomLimits || !(this._zoom <= e.getMinZoom() && 1 > s || this._zoom >= e.getMaxZoom() && s > 1)) { this._moved || (e._moveStart(!0), this._moved = !0), o.Util.cancelAnimFrame(this._animRequest); var a = o.bind(e._move, e, this._center, this._zoom, { pinch: !0, round: !1 }); - this._animRequest = o.Util.requestAnimFrame(a, this, !0), o.DomEvent.preventDefault(t) } } }, _onTouchEnd: function() { if (!this._moved || !this._zooming) return void(this._zooming = !1); - this._zooming = !1, o.Util.cancelAnimFrame(this._animRequest), o.DomEvent.off(e, "touchmove", this._onTouchMove).off(e, "touchend", this._onTouchEnd); var t = this._zoom; - t = this._map._limitZoom(t - this._startZoom > 0 ? Math.ceil(t) : Math.floor(t)), this._map._animateZoom(this._center, t, !0, !0) } }), o.Map.addInitHook("addHandler", "touchZoom", o.Map.TouchZoom), o.Map.mergeOptions({ tap: !0, tapTolerance: 15 }), o.Map.Tap = o.Handler.extend({ addHooks: function() { o.DomEvent.on(this._map._container, "touchstart", this._onDown, this) }, removeHooks: function() { o.DomEvent.off(this._map._container, "touchstart", this._onDown, this) }, _onDown: function(t) { if (t.touches) { if (o.DomEvent.preventDefault(t), this._fireClick = !0, t.touches.length > 1) return this._fireClick = !1, void clearTimeout(this._holdTimeout); var i = t.touches[0], - n = i.target; - this._startPos = this._newPos = new o.Point(i.clientX, i.clientY), n.tagName && "a" === n.tagName.toLowerCase() && o.DomUtil.addClass(n, "leaflet-active"), this._holdTimeout = setTimeout(o.bind(function() { this._isTapValid() && (this._fireClick = !1, this._onUp(), this._simulateEvent("contextmenu", i)) }, this), 1e3), this._simulateEvent("mousedown", i), o.DomEvent.on(e, { touchmove: this._onMove, touchend: this._onUp }, this) } }, _onUp: function(t) { if (clearTimeout(this._holdTimeout), o.DomEvent.off(e, { touchmove: this._onMove, touchend: this._onUp }, this), this._fireClick && t && t.changedTouches) { var i = t.changedTouches[0], - n = i.target; - n && n.tagName && "a" === n.tagName.toLowerCase() && o.DomUtil.removeClass(n, "leaflet-active"), this._simulateEvent("mouseup", i), this._isTapValid() && this._simulateEvent("click", i) } }, _isTapValid: function() { return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance }, _onMove: function(t) { var e = t.touches[0]; - this._newPos = new o.Point(e.clientX, e.clientY), this._simulateEvent("mousemove", e) }, _simulateEvent: function(i, n) { var o = e.createEvent("MouseEvents"); - o._simulated = !0, n.target._simulatedClick = !0, o.initMouseEvent(i, !0, !0, t, 1, n.screenX, n.screenY, n.clientX, n.clientY, !1, !1, !1, !1, 0, null), n.target.dispatchEvent(o) } }), o.Browser.touch && !o.Browser.pointer && o.Map.addInitHook("addHandler", "tap", o.Map.Tap), o.Map.mergeOptions({ boxZoom: !0 }), o.Map.BoxZoom = o.Handler.extend({ initialize: function(t) { this._map = t, this._container = t._container, this._pane = t._panes.overlayPane }, addHooks: function() { o.DomEvent.on(this._container, "mousedown", this._onMouseDown, this) }, removeHooks: function() { o.DomEvent.off(this._container, "mousedown", this._onMouseDown, this) }, moved: function() { return this._moved }, _resetState: function() { this._moved = !1 }, _onMouseDown: function(t) { return !t.shiftKey || 1 !== t.which && 1 !== t.button ? !1 : (this._resetState(), o.DomUtil.disableTextSelection(), o.DomUtil.disableImageDrag(), this._startPoint = this._map.mouseEventToContainerPoint(t), void o.DomEvent.on(e, { contextmenu: o.DomEvent.stop, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown }, this)) }, _onMouseMove: function(t) { this._moved || (this._moved = !0, this._box = o.DomUtil.create("div", "leaflet-zoom-box", this._container), o.DomUtil.addClass(this._container, "leaflet-crosshair"), this._map.fire("boxzoomstart")), this._point = this._map.mouseEventToContainerPoint(t); var e = new o.Bounds(this._point, this._startPoint), - i = e.getSize(); - o.DomUtil.setPosition(this._box, e.min), this._box.style.width = i.x + "px", this._box.style.height = i.y + "px" }, _finish: function() { this._moved && (o.DomUtil.remove(this._box), o.DomUtil.removeClass(this._container, "leaflet-crosshair")), o.DomUtil.enableTextSelection(), o.DomUtil.enableImageDrag(), o.DomEvent.off(e, { contextmenu: o.DomEvent.stop, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown }, this) }, _onMouseUp: function(t) { if ((1 === t.which || 1 === t.button) && (this._finish(), this._moved)) { setTimeout(o.bind(this._resetState, this), 0); var e = new o.LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); - this._map.fitBounds(e).fire("boxzoomend", { boxZoomBounds: e }) } }, _onKeyDown: function(t) { 27 === t.keyCode && this._finish() } }), o.Map.addInitHook("addHandler", "boxZoom", o.Map.BoxZoom), o.Map.mergeOptions({ keyboard: !0, keyboardPanOffset: 80, keyboardZoomOffset: 1 }), o.Map.Keyboard = o.Handler.extend({ keyCodes: { left: [37], right: [39], down: [40], up: [38], zoomIn: [187, 107, 61, 171], zoomOut: [189, 109, 54, 173] }, initialize: function(t) { this._map = t, this._setPanOffset(t.options.keyboardPanOffset), this._setZoomOffset(t.options.keyboardZoomOffset) }, addHooks: function() { var t = this._map._container; - t.tabIndex <= 0 && (t.tabIndex = "0"), o.DomEvent.on(t, { focus: this._onFocus, blur: this._onBlur, mousedown: this._onMouseDown }, this), this._map.on({ focus: this._addHooks, blur: this._removeHooks }, this) }, removeHooks: function() { this._removeHooks(), o.DomEvent.off(this._map._container, { focus: this._onFocus, blur: this._onBlur, mousedown: this._onMouseDown }, this), this._map.off({ focus: this._addHooks, blur: this._removeHooks }, this) }, _onMouseDown: function() { if (!this._focused) { var i = e.body, - n = e.documentElement, - o = i.scrollTop || n.scrollTop, - s = i.scrollLeft || n.scrollLeft; - this._map._container.focus(), t.scrollTo(s, o) } }, _onFocus: function() { this._focused = !0, this._map.fire("focus") }, _onBlur: function() { this._focused = !1, this._map.fire("blur") }, _setPanOffset: function(t) { var e, i, n = this._panKeys = {}, - o = this.keyCodes; for (e = 0, i = o.left.length; i > e; e++) n[o.left[e]] = [-1 * t, 0]; for (e = 0, i = o.right.length; i > e; e++) n[o.right[e]] = [t, 0]; for (e = 0, i = o.down.length; i > e; e++) n[o.down[e]] = [0, t]; for (e = 0, i = o.up.length; i > e; e++) n[o.up[e]] = [0, -1 * t] }, _setZoomOffset: function(t) { var e, i, n = this._zoomKeys = {}, - o = this.keyCodes; for (e = 0, i = o.zoomIn.length; i > e; e++) n[o.zoomIn[e]] = t; for (e = 0, i = o.zoomOut.length; i > e; e++) n[o.zoomOut[e]] = -t }, _addHooks: function() { o.DomEvent.on(e, "keydown", this._onKeyDown, this) }, _removeHooks: function() { o.DomEvent.off(e, "keydown", this._onKeyDown, this) }, _onKeyDown: function(t) { if (!(t.altKey || t.ctrlKey || t.metaKey)) { var e, i = t.keyCode, - n = this._map; if (i in this._panKeys) { if (n._panAnim && n._panAnim._inProgress) return; - e = this._panKeys[i], t.shiftKey && (e = o.point(e).multiplyBy(3)), n.panBy(e), n.options.maxBounds && n.panInsideBounds(n.options.maxBounds) } else if (i in this._zoomKeys) n.setZoom(n.getZoom() + (t.shiftKey ? 3 : 1) * this._zoomKeys[i]); - else { if (27 !== i) return; - n.closePopup() } - o.DomEvent.stop(t) } } }), o.Map.addInitHook("addHandler", "keyboard", o.Map.Keyboard), o.Handler.MarkerDrag = o.Handler.extend({ initialize: function(t) { this._marker = t }, addHooks: function() { var t = this._marker._icon; - this._draggable || (this._draggable = new o.Draggable(t, t, !0)), this._draggable.on({ dragstart: this._onDragStart, drag: this._onDrag, dragend: this._onDragEnd }, this).enable(), o.DomUtil.addClass(t, "leaflet-marker-draggable") }, removeHooks: function() { this._draggable.off({ dragstart: this._onDragStart, drag: this._onDrag, dragend: this._onDragEnd }, this).disable(), this._marker._icon && o.DomUtil.removeClass(this._marker._icon, "leaflet-marker-draggable") }, moved: function() { return this._draggable && this._draggable._moved }, _onDragStart: function() { this._marker.closePopup().fire("movestart").fire("dragstart") }, _onDrag: function(t) { var e = this._marker, - i = e._shadow, - n = o.DomUtil.getPosition(e._icon), - s = e._map.layerPointToLatLng(n); - i && o.DomUtil.setPosition(i, n), e._latlng = s, t.latlng = s, e.fire("move", t).fire("drag", t) }, _onDragEnd: function(t) { this._marker.fire("moveend").fire("dragend", t) } }), o.Control = o.Class.extend({ options: { position: "topright" }, initialize: function(t) { o.setOptions(this, t) }, getPosition: function() { return this.options.position }, setPosition: function(t) { var e = this._map; return e && e.removeControl(this), this.options.position = t, e && e.addControl(this), this }, getContainer: function() { return this._container }, addTo: function(t) { this.remove(), this._map = t; var e = this._container = this.onAdd(t), - i = this.getPosition(), - n = t._controlCorners[i]; return o.DomUtil.addClass(e, "leaflet-control"), -1 !== i.indexOf("bottom") ? n.insertBefore(e, n.firstChild) : n.appendChild(e), this }, remove: function() { return this._map ? (o.DomUtil.remove(this._container), this.onRemove && this.onRemove(this._map), this._map = null, this) : this }, _refocusOnMap: function(t) { this._map && t && t.screenX > 0 && t.screenY > 0 && this._map.getContainer().focus() } }), o.control = function(t) { return new o.Control(t) }, o.Map.include({ addControl: function(t) { return t.addTo(this), this }, removeControl: function(t) { return t.remove(), this }, _initControlPos: function() { - function t(t, s) { var r = i + t + " " + i + s; - e[t + s] = o.DomUtil.create("div", r, n) } var e = this._controlCorners = {}, - i = "leaflet-", - n = this._controlContainer = o.DomUtil.create("div", i + "control-container", this._container); - t("top", "left"), t("top", "right"), t("bottom", "left"), t("bottom", "right") }, _clearControlPos: function() { o.DomUtil.remove(this._controlContainer) } }), o.Control.Zoom = o.Control.extend({ options: { position: "topleft", zoomInText: "+", zoomInTitle: "Zoom in", zoomOutText: "-", zoomOutTitle: "Zoom out" }, onAdd: function(t) { var e = "leaflet-control-zoom", - i = o.DomUtil.create("div", e + " leaflet-bar"), - n = this.options; return this._zoomInButton = this._createButton(n.zoomInText, n.zoomInTitle, e + "-in", i, this._zoomIn), this._zoomOutButton = this._createButton(n.zoomOutText, n.zoomOutTitle, e + "-out", i, this._zoomOut), this._updateDisabled(), t.on("zoomend zoomlevelschange", this._updateDisabled, this), i }, onRemove: function(t) { t.off("zoomend zoomlevelschange", this._updateDisabled, this) }, disable: function() { return this._disabled = !0, this._updateDisabled(), this }, enable: function() { return this._disabled = !1, this._updateDisabled(), this }, _zoomIn: function(t) { this._disabled || this._map.zoomIn(t.shiftKey ? 3 : 1) }, _zoomOut: function(t) { this._disabled || this._map.zoomOut(t.shiftKey ? 3 : 1) }, _createButton: function(t, e, i, n, s) { var r = o.DomUtil.create("a", i, n); return r.innerHTML = t, r.href = "#", r.title = e, o.DomEvent.on(r, "mousedown dblclick", o.DomEvent.stopPropagation).on(r, "click", o.DomEvent.stop).on(r, "click", s, this).on(r, "click", this._refocusOnMap, this), r }, _updateDisabled: function() { var t = this._map, - e = "leaflet-disabled"; - o.DomUtil.removeClass(this._zoomInButton, e), o.DomUtil.removeClass(this._zoomOutButton, e), (this._disabled || t._zoom === t.getMinZoom()) && o.DomUtil.addClass(this._zoomOutButton, e), (this._disabled || t._zoom === t.getMaxZoom()) && o.DomUtil.addClass(this._zoomInButton, e) } }), o.Map.mergeOptions({ zoomControl: !0 }), o.Map.addInitHook(function() { this.options.zoomControl && (this.zoomControl = new o.Control.Zoom, this.addControl(this.zoomControl)) }), o.control.zoom = function(t) { return new o.Control.Zoom(t) }, o.Control.Attribution = o.Control.extend({ options: { position: "bottomright", prefix: 'Leaflet' }, initialize: function(t) { o.setOptions(this, t), this._attributions = {} }, onAdd: function(t) { this._container = o.DomUtil.create("div", "leaflet-control-attribution"), o.DomEvent && o.DomEvent.disableClickPropagation(this._container); for (var e in t._layers) t._layers[e].getAttribution && this.addAttribution(t._layers[e].getAttribution()); return this._update(), this._container }, setPrefix: function(t) { return this.options.prefix = t, this._update(), this }, addAttribution: function(t) { return t ? (this._attributions[t] || (this._attributions[t] = 0), this._attributions[t]++, this._update(), this) : this }, removeAttribution: function(t) { return t ? (this._attributions[t] && (this._attributions[t]--, this._update()), this) : this }, _update: function() { if (this._map) { var t = []; for (var e in this._attributions) this._attributions[e] && t.push(e); var i = []; - this.options.prefix && i.push(this.options.prefix), t.length && i.push(t.join(", ")), this._container.innerHTML = i.join(" | ") } } }), o.Map.mergeOptions({ attributionControl: !0 }), o.Map.addInitHook(function() { this.options.attributionControl && (this.attributionControl = (new o.Control.Attribution).addTo(this)) }), o.control.attribution = function(t) { return new o.Control.Attribution(t) }, o.Control.Scale = o.Control.extend({ options: { position: "bottomleft", maxWidth: 100, metric: !0, imperial: !0 }, onAdd: function(t) { var e = "leaflet-control-scale", - i = o.DomUtil.create("div", e), - n = this.options; return this._addScales(n, e + "-line", i), t.on(n.updateWhenIdle ? "moveend" : "move", this._update, this), t.whenReady(this._update, this), i }, onRemove: function(t) { t.off(this.options.updateWhenIdle ? "moveend" : "move", this._update, this) }, _addScales: function(t, e, i) { t.metric && (this._mScale = o.DomUtil.create("div", e, i)), t.imperial && (this._iScale = o.DomUtil.create("div", e, i)) }, _update: function() { var t = this._map, - e = t.getSize().y / 2, - i = t.distance(t.containerPointToLatLng([0, e]), t.containerPointToLatLng([this.options.maxWidth, e])); - this._updateScales(i) }, _updateScales: function(t) { this.options.metric && t && this._updateMetric(t), this.options.imperial && t && this._updateImperial(t) }, _updateMetric: function(t) { var e = this._getRoundNum(t), - i = 1e3 > e ? e + " m" : e / 1e3 + " km"; - this._updateScale(this._mScale, i, e / t) }, _updateImperial: function(t) { var e, i, n, o = 3.2808399 * t; - o > 5280 ? (e = o / 5280, i = this._getRoundNum(e), this._updateScale(this._iScale, i + " mi", i / e)) : (n = this._getRoundNum(o), this._updateScale(this._iScale, n + " ft", n / o)) }, _updateScale: function(t, e, i) { t.style.width = Math.round(this.options.maxWidth * i) + "px", t.innerHTML = e }, _getRoundNum: function(t) { var e = Math.pow(10, (Math.floor(t) + "").length - 1), - i = t / e; return i = i >= 10 ? 10 : i >= 5 ? 5 : i >= 3 ? 3 : i >= 2 ? 2 : 1, e * i } }), o.control.scale = function(t) { return new o.Control.Scale(t) }, o.Control.Layers = o.Control.extend({ options: { collapsed: !0, position: "topright", autoZIndex: !0, hideSingleBase: !1 }, initialize: function(t, e, i) { o.setOptions(this, i), this._layers = {}, this._lastZIndex = 0, this._handlingClick = !1; for (var n in t) this._addLayer(t[n], n); for (n in e) this._addLayer(e[n], n, !0) }, onAdd: function(t) { return this._initLayout(), this._update(), this._map = t, t.on("zoomend", this._checkDisabledLayers, this), this._container }, onRemove: function() { this._map.off("zoomend", this._checkDisabledLayers, this) }, addBaseLayer: function(t, e) { return this._addLayer(t, e), this._update() }, addOverlay: function(t, e) { return this._addLayer(t, e, !0), this._update() }, removeLayer: function(t) { return t.off("add remove", this._onLayerChange, this), delete this._layers[o.stamp(t)], this._update() }, _initLayout: function() { var t = "leaflet-control-layers", - e = this._container = o.DomUtil.create("div", t); - e.setAttribute("aria-haspopup", !0), o.DomEvent.disableClickPropagation(e), o.Browser.touch || o.DomEvent.disableScrollPropagation(e); var i = this._form = o.DomUtil.create("form", t + "-list"); if (this.options.collapsed) { o.Browser.android || o.DomEvent.on(e, { mouseenter: this._expand, mouseleave: this._collapse }, this); var n = this._layersLink = o.DomUtil.create("a", t + "-toggle", e); - n.href = "#", n.title = "Layers", o.Browser.touch ? o.DomEvent.on(n, "click", o.DomEvent.stop).on(n, "click", this._expand, this) : o.DomEvent.on(n, "focus", this._expand, this), o.DomEvent.on(i, "click", function() { setTimeout(o.bind(this._onInputClick, this), 0) }, this), this._map.on("click", this._collapse, this) } else this._expand(); - this._baseLayersList = o.DomUtil.create("div", t + "-base", i), this._separator = o.DomUtil.create("div", t + "-separator", i), this._overlaysList = o.DomUtil.create("div", t + "-overlays", i), e.appendChild(i) }, _addLayer: function(t, e, i) { t.on("add remove", this._onLayerChange, this); var n = o.stamp(t); - this._layers[n] = { layer: t, name: e, overlay: i }, this.options.autoZIndex && t.setZIndex && (this._lastZIndex++, t.setZIndex(this._lastZIndex)) }, _update: function() { if (!this._container) return this; - o.DomUtil.empty(this._baseLayersList), o.DomUtil.empty(this._overlaysList); var t, e, i, n, s = 0; for (i in this._layers) n = this._layers[i], this._addItem(n), e = e || n.overlay, t = t || !n.overlay, s += n.overlay ? 0 : 1; return this.options.hideSingleBase && (t = t && s > 1, this._baseLayersList.style.display = t ? "" : "none"), this._separator.style.display = e && t ? "" : "none", this }, _onLayerChange: function(t) { this._handlingClick || this._update(); var e = this._layers[o.stamp(t.target)], - i = e.overlay ? "add" === t.type ? "overlayadd" : "overlayremove" : "add" === t.type ? "baselayerchange" : null; - i && this._map.fire(i, e) }, _createRadioElement: function(t, i) { var n = '", - o = e.createElement("div"); return o.innerHTML = n, o.firstChild }, _addItem: function(t) { var i, n = e.createElement("label"), - s = this._map.hasLayer(t.layer); - t.overlay ? (i = e.createElement("input"), i.type = "checkbox", i.className = "leaflet-control-layers-selector", i.defaultChecked = s) : i = this._createRadioElement("leaflet-base-layers", s), i.layerId = o.stamp(t.layer), o.DomEvent.on(i, "click", this._onInputClick, this); var r = e.createElement("span"); - r.innerHTML = " " + t.name; var a = e.createElement("div"); - n.appendChild(a), a.appendChild(i), a.appendChild(r); var h = t.overlay ? this._overlaysList : this._baseLayersList; return h.appendChild(n), this._checkDisabledLayers(), n }, _onInputClick: function() { var t, e, i, n = this._form.getElementsByTagName("input"), - o = [], - s = []; - this._handlingClick = !0; for (var r = n.length - 1; r >= 0; r--) t = n[r], e = this._layers[t.layerId].layer, i = this._map.hasLayer(e), t.checked && !i ? o.push(e) : !t.checked && i && s.push(e); for (r = 0; r < s.length; r++) this._map.removeLayer(s[r]); for (r = 0; r < o.length; r++) this._map.addLayer(o[r]); - this._handlingClick = !1, this._refocusOnMap() }, _expand: function() { o.DomUtil.addClass(this._container, "leaflet-control-layers-expanded"), this._form.style.height = null; var t = this._map._size.y - (this._container.offsetTop + 50); - t < this._form.clientHeight ? (o.DomUtil.addClass(this._form, "leaflet-control-layers-scrollbar"), this._form.style.height = t + "px") : o.DomUtil.removeClass(this._form, "leaflet-control-layers-scrollbar"), this._checkDisabledLayers() }, _collapse: function() { o.DomUtil.removeClass(this._container, "leaflet-control-layers-expanded") }, _checkDisabledLayers: function() { for (var t, e, n = this._form.getElementsByTagName("input"), o = this._map.getZoom(), s = n.length - 1; s >= 0; s--) t = n[s], e = this._layers[t.layerId].layer, t.disabled = e.options.minZoom !== i && o < e.options.minZoom || e.options.maxZoom !== i && o > e.options.maxZoom } }), o.control.layers = function(t, e, i) { return new o.Control.Layers(t, e, i) }, o.PosAnimation = o.Evented.extend({ run: function(t, e, i, n) { this.stop(), this._el = t, this._inProgress = !0, this._duration = i || .25, this._easeOutPower = 1 / Math.max(n || .5, .2), this._startPos = o.DomUtil.getPosition(t), this._offset = e.subtract(this._startPos), this._startTime = +new Date, this.fire("start"), this._animate() }, stop: function() { this._inProgress && (this._step(!0), this._complete()) }, _animate: function() { this._animId = o.Util.requestAnimFrame(this._animate, this), this._step() }, _step: function(t) { var e = +new Date - this._startTime, - i = 1e3 * this._duration; - i > e ? this._runFrame(this._easeOut(e / i), t) : (this._runFrame(1), this._complete()) }, _runFrame: function(t, e) { var i = this._startPos.add(this._offset.multiplyBy(t)); - e && i._round(), o.DomUtil.setPosition(this._el, i), this.fire("step") }, _complete: function() { o.Util.cancelAnimFrame(this._animId), this._inProgress = !1, this.fire("end") }, _easeOut: function(t) { return 1 - Math.pow(1 - t, this._easeOutPower) } }), o.Map.include({ setView: function(t, e, n) { if (e = e === i ? this._zoom : this._limitZoom(e), t = this._limitCenter(o.latLng(t), e, this.options.maxBounds), n = n || {}, this.stop(), this._loaded && !n.reset && n !== !0) { n.animate !== i && (n.zoom = o.extend({ animate: n.animate }, n.zoom), n.pan = o.extend({ animate: n.animate, duration: n.duration }, n.pan)); var s = this._zoom !== e ? this._tryAnimatedZoom && this._tryAnimatedZoom(t, e, n.zoom) : this._tryAnimatedPan(t, n.pan); if (s) return clearTimeout(this._sizeTimer), this } return this._resetView(t, e), this }, panBy: function(t, e) { if (t = o.point(t).round(), e = e || {}, !t.x && !t.y) return this.fire("moveend"); if (e.animate !== !0 && !this.getSize().contains(t)) return this._resetView(this.unproject(this.project(this.getCenter()).add(t)), this.getZoom()), this; if (this._panAnim || (this._panAnim = new o.PosAnimation, this._panAnim.on({ step: this._onPanTransitionStep, end: this._onPanTransitionEnd }, this)), e.noMoveStart || this.fire("movestart"), e.animate !== !1) { o.DomUtil.addClass(this._mapPane, "leaflet-pan-anim"); var i = this._getMapPanePos().subtract(t); - this._panAnim.run(this._mapPane, i, e.duration || .25, e.easeLinearity) } else this._rawPanBy(t), this.fire("move").fire("moveend"); return this }, _onPanTransitionStep: function() { this.fire("move") }, _onPanTransitionEnd: function() { o.DomUtil.removeClass(this._mapPane, "leaflet-pan-anim"), this.fire("moveend") }, _tryAnimatedPan: function(t, e) { var i = this._getCenterOffset(t)._floor(); return (e && e.animate) === !0 || this.getSize().contains(i) ? (this.panBy(i, e), !0) : !1 } }), o.Map.mergeOptions({ zoomAnimation: !0, zoomAnimationThreshold: 4 }); - var h = o.DomUtil.TRANSITION && o.Browser.any3d && !o.Browser.mobileOpera; - h && o.Map.addInitHook(function() { this._zoomAnimated = this.options.zoomAnimation, this._zoomAnimated && (this._createAnimProxy(), o.DomEvent.on(this._proxy, o.DomUtil.TRANSITION_END, this._catchTransitionEnd, this)) }), o.Map.include(h ? { - _createAnimProxy: function() { - var t = this._proxy = o.DomUtil.create("div", "leaflet-proxy leaflet-zoom-animated"); - this._panes.mapPane.appendChild(t), this.on("zoomanim", function(e) { var i = o.DomUtil.TRANSFORM, - n = t.style[i]; - o.DomUtil.setTransform(t, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)), n === t.style[i] && this._animatingZoom && this._onZoomTransitionEnd() }, this), this.on("load moveend", function() { - var e = this.getCenter(), - i = this.getZoom(); - o.DomUtil.setTransform(t, this.project(e, i), this.getZoomScale(i, 1)) - }, this) - }, - _catchTransitionEnd: function(t) { this._animatingZoom && t.propertyName.indexOf("transform") >= 0 && this._onZoomTransitionEnd() }, - _nothingToAnimate: function() { return !this._container.getElementsByClassName("leaflet-zoom-animated").length }, - _tryAnimatedZoom: function(t, e, i) { if (this._animatingZoom) return !0; if (i = i || {}, !this._zoomAnimated || i.animate === !1 || this._nothingToAnimate() || Math.abs(e - this._zoom) > this.options.zoomAnimationThreshold) return !1; var n = this.getZoomScale(e), - s = this._getCenterOffset(t)._divideBy(1 - 1 / n); return i.animate === !0 || this.getSize().contains(s) ? (o.Util.requestAnimFrame(function() { this._moveStart(!0)._animateZoom(t, e, !0) }, this), !0) : !1 }, - _animateZoom: function(t, e, i, n) { i && (this._animatingZoom = !0, this._animateToCenter = t, this._animateToZoom = e, o.DomUtil.addClass(this._mapPane, "leaflet-zoom-anim")), this.fire("zoomanim", { center: t, zoom: e, noUpdate: n }), setTimeout(o.bind(this._onZoomTransitionEnd, this), 250) }, - _onZoomTransitionEnd: function() { this._animatingZoom && (o.DomUtil.removeClass(this._mapPane, "leaflet-zoom-anim"), o.Util.requestAnimFrame(function() { this._animatingZoom = !1, this._move(this._animateToCenter, this._animateToZoom)._moveEnd(!0) }, this)) } - } : {}), o.Map.include({ flyTo: function(t, e, n) { - function s(t) { var e = (v * v - g * g + (t ? -1 : 1) * L * L * y * y) / (2 * (t ? v : g) * L * y); return Math.log(Math.sqrt(e * e + 1) - e) } - - function r(t) { return (Math.exp(t) - Math.exp(-t)) / 2 } - - function a(t) { return (Math.exp(t) + Math.exp(-t)) / 2 } - - function h(t) { return r(t) / a(t) } - - function l(t) { return g * (a(x) / a(x + P * t)) } - - function u(t) { return g * (a(x) * h(x + P * t) - r(x)) / L } - - function c(t) { return 1 - Math.pow(1 - t, 1.5) } - - function d() { var i = (Date.now() - b) / D, - n = c(i) * w; - 1 >= i ? (this._flyToFrame = o.Util.requestAnimFrame(d, this), this._move(this.unproject(_.add(m.subtract(_).multiplyBy(u(n) / y)), f), this.getScaleZoom(g / l(n), f), { flyTo: !0 })) : this._move(t, e)._moveEnd(!0) } if (n = n || {}, n.animate === !1 || !o.Browser.any3d) return this.setView(t, e, n); - this.stop(); var _ = this.project(this.getCenter()), - m = this.project(t), - p = this.getSize(), - f = this._zoom; - t = o.latLng(t), e = e === i ? f : e; var g = Math.max(p.x, p.y), - v = g * this.getZoomScale(f, e), - y = m.distanceTo(_) || 1, - P = 1.42, - L = P * P, - x = s(0), - b = Date.now(), - w = (s(1) - x) / P, - D = n.duration ? 1e3 * n.duration : 1e3 * w * .8; return this._moveStart(!0), d.call(this), this }, flyToBounds: function(t, e) { var i = this._getBoundsCenterZoom(t, e); return this.flyTo(i.center, i.zoom, e) } }), o.Map.include({ _defaultLocateOptions: { timeout: 1e4, watch: !1 }, locate: function(t) { if (t = this._locateOptions = o.extend({}, this._defaultLocateOptions, t), !("geolocation" in navigator)) return this._handleGeolocationError({ code: 0, message: "Geolocation not supported." }), this; var e = o.bind(this._handleGeolocationResponse, this), - i = o.bind(this._handleGeolocationError, this); return t.watch ? this._locationWatchId = navigator.geolocation.watchPosition(e, i, t) : navigator.geolocation.getCurrentPosition(e, i, t), this }, stopLocate: function() { return navigator.geolocation && navigator.geolocation.clearWatch && navigator.geolocation.clearWatch(this._locationWatchId), this._locateOptions && (this._locateOptions.setView = !1), this }, _handleGeolocationError: function(t) { var e = t.code, - i = t.message || (1 === e ? "permission denied" : 2 === e ? "position unavailable" : "timeout"); - this._locateOptions.setView && !this._loaded && this.fitWorld(), this.fire("locationerror", { code: e, message: "Geolocation error: " + i + "." }) }, _handleGeolocationResponse: function(t) { var e = t.coords.latitude, - i = t.coords.longitude, - n = new o.LatLng(e, i), - s = n.toBounds(t.coords.accuracy), - r = this._locateOptions; if (r.setView) { var a = this.getBoundsZoom(s); - this.setView(n, r.maxZoom ? Math.min(a, r.maxZoom) : a) } var h = { latlng: n, bounds: s, timestamp: t.timestamp }; for (var l in t.coords) "number" == typeof t.coords[l] && (h[l] = t.coords[l]); - this.fire("locationfound", h) } }) -}(window, document); From 9e791efc8200d4f7f156fe047cc223a50d4eff23 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Jul 2023 21:44:43 +0530 Subject: [PATCH 069/107] refactor: client side accounts controller --- .../doctype/payment_entry/payment_entry.js | 29 +- .../doctype/pos_invoice/pos_invoice.js | 1 + .../doctype/pos_profile/pos_profile.js | 4 +- .../purchase_invoice/purchase_invoice.js | 27 +- .../purchase_taxes_and_charges_template.js | 45 +- .../doctype/sales_invoice/sales_invoice.js | 4 + .../sales_taxes_and_charges_template.js | 5 +- .../doctype/purchase_order/purchase_order.js | 4 + erpnext/public/js/controllers/accounts.js | 542 ++++++++---------- erpnext/public/js/erpnext.bundle.js | 1 + .../doctype/sales_order/sales_order.js | 3 + .../doctype/delivery_note/delivery_note.js | 4 + .../purchase_receipt/purchase_receipt.js | 3 + 13 files changed, 334 insertions(+), 338 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 0701435dfc..b844f7419b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -1,10 +1,12 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -{% include "erpnext/public/js/controllers/accounts.js" %} frappe.provide("erpnext.accounts.dimensions"); cur_frm.cscript.tax_table = "Advance Taxes and Charges"; +erpnext.accounts.taxes.setup_tax_validations("Payment Entry"); +erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges"); + frappe.ui.form.on('Payment Entry', { onload: function(frm) { frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Repost Payment Ledger"]; @@ -106,12 +108,11 @@ frappe.ui.form.on('Payment Entry', { }); frm.set_query("reference_doctype", "references", function() { + let doctypes = ["Journal Entry"]; if (frm.doc.party_type == "Customer") { - var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; + doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"]; } else if (frm.doc.party_type == "Supplier") { - var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"]; - } else { - var doctypes = ["Journal Entry"]; + doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"]; } return { @@ -165,6 +166,7 @@ frappe.ui.form.on('Payment Entry', { }, company: function(frm) { + frm.trigger('party'); frm.events.hide_unhide_fields(frm); frm.events.set_dynamic_labels(frm); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); @@ -287,6 +289,13 @@ frappe.ui.form.on('Payment Entry', { } }, + mode_of_payment: function(frm) { + erpnext.accounts.pos.get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){ + let payment_account_field = frm.doc.payment_type == "Receive" ? "paid_to" : "paid_from"; + frm.set_value(payment_account_field, account); + }) + }, + party_type: function(frm) { let party_types = Object.keys(frappe.boot.party_account_types); @@ -319,10 +328,6 @@ frappe.ui.form.on('Payment Entry', { } }, - company: function(frm){ - frm.trigger('party'); - }, - party: function(frm) { if (frm.doc.contact_email || frm.doc.contact_person) { frm.set_value("contact_email", ""); @@ -1106,7 +1111,7 @@ frappe.ui.form.on('Payment Entry', { if (tax.charge_type === 'On Net Total') { tax.charge_type = 'On Paid Amount'; } - me.frm.add_child("taxes", tax); + frm.add_child("taxes", tax); } frm.events.apply_taxes(frm); frm.events.set_unallocated_amount(frm); @@ -1222,7 +1227,7 @@ frappe.ui.form.on('Payment Entry', { tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; } else { tax.grand_total_fraction_for_current_item = - me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item + + frm.doc["taxes"][i-1].grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item; } @@ -1269,7 +1274,7 @@ frappe.ui.form.on('Payment Entry', { } }); - $.each(me.frm.doc["taxes"] || [], function(i, tax) { + $.each(frm.doc["taxes"] || [], function(i, tax) { let current_tax_amount = frm.events.get_current_tax_amount(frm, tax); // Adjust divisional loss to the last item diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 32e267f33c..30c841e02b 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -4,6 +4,7 @@ {% include 'erpnext/selling/sales_common.js' %}; frappe.provide("erpnext.accounts"); +erpnext.accounts.pos.setup("POS Invoice"); erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController { settings = {}; diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index 813d20dbf9..0a89aee8e9 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -1,8 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -{% include "erpnext/public/js/controllers/accounts.js" %} - frappe.ui.form.on('POS Profile', { setup: function(frm) { frm.set_query("selling_price_list", function() { @@ -148,4 +146,4 @@ frappe.ui.form.on('POS Profile', { frm.toggle_display('expense_account', erpnext.is_perpetual_inventory_enabled(frm.doc.company)); } -}); +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 6a558ca606..0f581e20da 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -4,6 +4,10 @@ frappe.provide("erpnext.accounts"); {% include 'erpnext/public/js/controllers/buying.js' %}; +erpnext.accounts.payment_triggers.setup("Purchase Invoice"); +erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); +erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice"); + erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController { setup(doc) { this.setup_posting_date_time_check(); @@ -506,7 +510,8 @@ frappe.ui.form.on("Purchase Invoice", { setup: function(frm) { frm.custom_make_buttons = { 'Purchase Invoice': 'Return / Debit Note', - 'Payment Entry': 'Payment' + 'Payment Entry': 'Payment', + 'Landed Cost Voucher': function () { frm.trigger('create_landed_cost_voucher') }, } frm.set_query("additional_discount_account", function() { @@ -544,6 +549,26 @@ frappe.ui.form.on("Purchase Invoice", { frm.events.add_custom_buttons(frm); }, + mode_of_payment: function(frm) { + get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){ + frm.set_value('cash_bank_account', account); + }) + }, + + create_landed_cost_voucher: function (frm) { + let lcv = frappe.model.get_new_doc('Landed Cost Voucher'); + lcv.company = frm.doc.company; + + let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice'); + lcv_receipt.receipt_document_type = 'Purchase Invoice'; + lcv_receipt.receipt_document = frm.doc.name; + lcv_receipt.supplier = frm.doc.supplier; + lcv_receipt.grand_total = frm.doc.grand_total; + lcv.purchase_receipts = [lcv_receipt]; + + frappe.set_route("Form", lcv.doctype, lcv.name); + }, + add_custom_buttons: function(frm) { if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) { frm.add_custom_button(__('Purchase Receipt'), () => { diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js index eb0ea7fef8..78dc4bee1a 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js @@ -1,30 +1,31 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; +erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template"); +erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); -{% include "erpnext/public/js/controllers/accounts.js" %} +frappe.ui.form.on("Purchase Taxes and Charges", { + add_deduct_tax(doc, cdt, cdn) { + let d = locals[cdt][cdn]; -frappe.ui.form.on("Purchase Taxes and Charges", "add_deduct_tax", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + if(!d.category && d.add_deduct_tax) { + frappe.msgprint(__("Please select Category first")); + d.add_deduct_tax = ''; + } + else if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') { + frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'")); + d.add_deduct_tax = ''; + } + refresh_field('add_deduct_tax', d.name, 'taxes'); + }, - if(!d.category && d.add_deduct_tax) { - frappe.msgprint(__("Please select Category first")); - d.add_deduct_tax = ''; + category(doc, cdt, cdn) { + let d = locals[cdt][cdn]; + + if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') { + frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'")); + d.add_deduct_tax = ''; + } + refresh_field('add_deduct_tax', d.name, 'taxes'); } - else if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') { - frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'")); - d.add_deduct_tax = ''; - } - refresh_field('add_deduct_tax', d.name, 'taxes'); -}); - -frappe.ui.form.on("Purchase Taxes and Charges", "category", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - - if (d.category != 'Total' && d.add_deduct_tax == 'Deduct') { - frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Vaulation and Total'")); - d.add_deduct_tax = ''; - } - refresh_field('add_deduct_tax', d.name, 'taxes'); }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8753ebc3ba..389c1644b2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -4,6 +4,10 @@ {% include 'erpnext/selling/sales_common.js' %}; frappe.provide("erpnext.accounts"); +erpnext.accounts.taxes.setup_tax_validations("Sales Invoice"); +erpnext.accounts.payment_triggers.setup("Sales Invoice"); +erpnext.accounts.pos.setup("Sales Invoice"); +erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends erpnext.selling.SellingController { setup(doc) { diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js index 066c4eae43..00e8b621c0 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js @@ -1,6 +1,5 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.cscript.tax_table = "Sales Taxes and Charges"; - -{% include "erpnext/public/js/controllers/accounts.js" %} +erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template"); +erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 8fa8f30554..97b7cf874c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -5,6 +5,10 @@ frappe.provide("erpnext.buying"); frappe.provide("erpnext.accounts.dimensions"); {% include 'erpnext/public/js/controllers/buying.js' %}; +erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); +erpnext.accounts.taxes.setup_tax_validations("Purchase Order"); + + frappe.ui.form.on("Purchase Order", { setup: function(frm) { diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 47b88a002b..d03a07cf03 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -3,319 +3,267 @@ // get tax rate frappe.provide("erpnext.taxes"); -frappe.provide("erpnext.taxes.flags"); -frappe.ui.form.on(cur_frm.doctype, { - setup: function(frm) { - // set conditional display for rate column in taxes - $(frm.wrapper).on('grid-row-render', function(e, grid_row) { - if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) { - erpnext.taxes.set_conditional_mandatory_rate_or_amount(grid_row); +erpnext.accounts.taxes = { + setup_tax_validations: function(doctype) { + let me = this; + frappe.ui.form.on(doctype, { + setup: function(frm) { + // set conditional display for rate column in taxes + $(frm.wrapper).on('grid-row-render', function(e, grid_row) { + if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) { + me.set_conditional_mandatory_rate_or_amount(grid_row); + } + }); + }, + onload: function(frm) { + if(frm.get_field("taxes")) { + frm.set_query("account_head", "taxes", function(doc) { + if(frm.cscript.tax_table == "Sales Taxes and Charges") { + var account_type = ["Tax", "Chargeable", "Expense Account"]; + } else { + var account_type = ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"]; + } + + return { + query: "erpnext.controllers.queries.tax_account_query", + filters: { + "account_type": account_type, + "company": doc.company, + "disabled": 0 + } + } + }); + frm.set_query("cost_center", "taxes", function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0 + } + }; + }); + } + }, + validate: function(frm) { + // neither is absolutely mandatory + if(frm.get_docfield("taxes")) { + frm.get_docfield("taxes", "rate").reqd = 0; + frm.get_docfield("taxes", "tax_amount").reqd = 0; + } + + }, + taxes_on_form_rendered: function(frm) { + me.set_conditional_mandatory_rate_or_amount(frm.open_grid_row()); + }, + }); + }, + + set_conditional_mandatory_rate_or_amount: function(grid_row) { + if(grid_row) { + if(grid_row.doc.charge_type==="Actual") { + grid_row.toggle_editable("tax_amount", true); + grid_row.toggle_reqd("tax_amount", true); + grid_row.toggle_editable("rate", false); + grid_row.toggle_reqd("rate", false); + } else { + grid_row.toggle_editable("rate", true); + grid_row.toggle_reqd("rate", true); + grid_row.toggle_editable("tax_amount", false); + grid_row.toggle_reqd("tax_amount", false); + } + } + }, + + validate_taxes_and_charges: function(cdt, cdn) { + let d = locals[cdt][cdn]; + let msg = ""; + + if (d.account_head && !d.description) { + // set description from account head + d.description = d.account_head.split(' - ').slice(0, -1).join(' - '); + } + + if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) { + msg = __("Please select Charge Type first"); + d.row_id = ""; + d.rate = d.tax_amount = 0.0; + } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) { + msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"); + d.row_id = ""; + } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { + if (d.idx == 1) { + msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"); + d.charge_type = ''; + } else if (!d.row_id) { + msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]); + d.row_id = ""; + } else if (d.row_id && d.row_id >= d.idx) { + msg = __("Cannot refer row number greater than or equal to current row number for this Charge type"); + d.row_id = ""; + } + } + if (msg) { + frappe.validated = false; + refresh_field("taxes"); + frappe.throw(msg); + } + + }, + + setup_tax_filters: function(doctype) { + let me = this; + frappe.ui.form.on(doctype, { + account_head: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + + if (doc.docstatus == 1) { + // Should not trigger any changes on change post submit + return; + } + + if(!d.charge_type && d.account_head){ + frappe.msgprint(__("Please select Charge Type first")); + frappe.model.set_value(cdt, cdn, "account_head", ""); + } else if (d.account_head) { + frappe.call({ + type:"GET", + method: "erpnext.controllers.accounts_controller.get_tax_rate", + args: {"account_head":d.account_head}, + callback: function(r) { + if (d.charge_type!=="Actual") { + frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); + } + frappe.model.set_value(cdt, cdn, "description", r.message.account_name); + } + }) + } + }, + row_id: function(frm, cdt, cdn) { + me.validate_taxes_and_charges(cdt, cdn); + }, + rate: function(frm, cdt, cdn) { + me.validate_taxes_and_charges(cdt, cdn); + }, + tax_amount: function(frm, cdt, cdn) { + me.validate_taxes_and_charges(cdt, cdn); + }, + charge_type: function(frm, cdt, cdn) { + me.validate_taxes_and_charges(cdt, cdn); + let open_form = frm.open_grid_row(); + if(open_form) { + me.set_conditional_mandatory_rate_or_amount(open_form); + } else { + // apply in current row + me.set_conditional_mandatory_rate_or_amount(frm.get_field('taxes').grid.get_row(cdn)); + } + }, + included_in_print_rate: function(frm, cdt, cdn) { + let tax = frappe.get_doc(cdt, cdn); + try { + me.validate_taxes_and_charges(cdt, cdn); + me.validate_inclusive_tax(tax); + } catch(e) { + tax.included_in_print_rate = 0; + refresh_field("included_in_print_rate", tax.name, tax.parentfield); + throw e; + } } }); }, - onload: function(frm) { - if(frm.get_field("taxes")) { - frm.set_query("account_head", "taxes", function(doc) { - if(frm.cscript.tax_table == "Sales Taxes and Charges") { - var account_type = ["Tax", "Chargeable", "Expense Account"]; - } else { - var account_type = ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"]; + + validate_inclusive_tax: function(tax) { + let actual_type_error = function() { + var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]) + frappe.throw(msg); + }; + + let on_previous_row_error = function(row_range) { + var msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included", + [tax.idx, __(tax.doctype), tax.charge_type, row_range]) + frappe.throw(msg); + }; + + if(cint(tax.included_in_print_rate)) { + if(tax.charge_type == "Actual") { + // inclusive tax cannot be of type Actual + actual_type_error(); + } else if(tax.charge_type == "On Previous Row Amount" && + !cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate) + ) { + // referred row should also be an inclusive tax + on_previous_row_error(tax.row_id); + } else if(tax.charge_type == "On Previous Row Total") { + var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), + function(t) { return cint(t.included_in_print_rate) ? null : t; }); + if(taxes_not_included.length > 0) { + // all rows above this tax should be inclusive + on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id); } - - return { - query: "erpnext.controllers.queries.tax_account_query", - filters: { - "account_type": account_type, - "company": doc.company, - "disabled": 0 - } - } - }); - frm.set_query("cost_center", "taxes", function(doc) { - return { - filters: { - "company": doc.company, - "is_group": 0 - } - }; - }); - } - }, - validate: function(frm) { - // neither is absolutely mandatory - if(frm.get_docfield("taxes")) { - frm.get_docfield("taxes", "rate").reqd = 0; - frm.get_docfield("taxes", "tax_amount").reqd = 0; - } - - }, - taxes_on_form_rendered: function(frm) { - erpnext.taxes.set_conditional_mandatory_rate_or_amount(frm.open_grid_row()); - }, - - allocate_advances_automatically: function(frm) { - frm.trigger('fetch_advances'); - }, - - only_include_allocated_payments: function(frm) { - frm.trigger('fetch_advances'); - }, - - fetch_advances: function(frm) { - if(frm.doc.allocate_advances_automatically) { - frappe.call({ - doc: frm.doc, - method: "set_advances", - callback: function(r, rt) { - refresh_field("advances"); - } - }) - } - } -}); - -frappe.ui.form.on('Sales Invoice Payment', { - mode_of_payment: function(frm, cdt, cdn) { - var d = locals[cdt][cdn]; - get_payment_mode_account(frm, d.mode_of_payment, function(account){ - frappe.model.set_value(cdt, cdn, 'account', account) - }) - } -}); - -frappe.ui.form.on("Sales Invoice", { - payment_terms_template: function() { - cur_frm.trigger("disable_due_date"); - } -}); - -frappe.ui.form.on('Purchase Invoice', { - setup: (frm) => { - frm.make_methods = { - 'Landed Cost Voucher': function () { frm.trigger('create_landedcost_voucher') }, - } - }, - - mode_of_payment: function(frm) { - get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){ - frm.set_value('cash_bank_account', account); - }) - }, - - payment_terms_template: function() { - cur_frm.trigger("disable_due_date"); - }, - - create_landedcost_voucher: function (frm) { - let lcv = frappe.model.get_new_doc('Landed Cost Voucher'); - lcv.company = frm.doc.company; - - let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice'); - lcv_receipt.receipt_document_type = 'Purchase Invoice'; - lcv_receipt.receipt_document = frm.doc.name; - lcv_receipt.supplier = frm.doc.supplier; - lcv_receipt.grand_total = frm.doc.grand_total; - lcv.purchase_receipts = [lcv_receipt]; - - frappe.set_route("Form", lcv.doctype, lcv.name); - } -}); - -frappe.ui.form.on("Payment Schedule", { - payment_schedule_remove: function() { - cur_frm.trigger("disable_due_date"); - }, - -}); - -frappe.ui.form.on('Payment Entry', { - mode_of_payment: function(frm) { - get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){ - var payment_account_field = frm.doc.payment_type == "Receive" ? "paid_to" : "paid_from"; - frm.set_value(payment_account_field, account); - }) - } -}) - -frappe.ui.form.on('Salary Structure', { - mode_of_payment: function(frm) { - get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){ - frm.set_value("payment_account", account); - }) - } -}) - -var get_payment_mode_account = function(frm, mode_of_payment, callback) { - if(!frm.doc.company) { - frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")}); - } - - if(!mode_of_payment) { - return; - } - - return frappe.call({ - method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", - args: { - "mode_of_payment": mode_of_payment, - "company": frm.doc.company - }, - callback: function(r, rt) { - if(r.message) { - callback(r.message.account) + } else if(tax.category == "Valuation") { + frappe.throw(__("Valuation type charges can not marked as Inclusive")); } } - }); + } } -cur_frm.cscript.account_head = function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; +erpnext.accounts.payment_triggers = { + setup: function(doctype) { + frappe.form.ui.on(doctype, { + allocate_advances_automatically(frm) { + frm.trigger('fetch_advances'); + }, - if (doc.docstatus == 1) { - // Should not trigger any changes on change post submit - return; - } + only_include_allocated_payments(frm) { + frm.trigger('fetch_advances'); + }, - if(!d.charge_type && d.account_head){ - frappe.msgprint(__("Please select Charge Type first")); - frappe.model.set_value(cdt, cdn, "account_head", ""); - } else if (d.account_head) { - frappe.call({ - type:"GET", - method: "erpnext.controllers.accounts_controller.get_tax_rate", - args: {"account_head":d.account_head}, - callback: function(r) { - if (d.charge_type!=="Actual") { - frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); + fetch_advances(frm) { + if(frm.doc.allocate_advances_automatically) { + frappe.call({ + doc: frm.doc, + method: "set_advances", + callback: function(r, rt) { + refresh_field("advances"); + } + }) } - frappe.model.set_value(cdt, cdn, "description", r.message.account_name); } - }) - } + }); + }, } -cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) { - var d = locals[cdt][cdn]; - var msg = ""; - - if (d.account_head && !d.description) { - // set description from account head - d.description = d.account_head.split(' - ').slice(0, -1).join(' - '); - } - - if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) { - msg = __("Please select Charge Type first"); - d.row_id = ""; - d.rate = d.tax_amount = 0.0; - } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) { - msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"); - d.row_id = ""; - } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) { - if (d.idx == 1) { - msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"); - d.charge_type = ''; - } else if (!d.row_id) { - msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]); - d.row_id = ""; - } else if (d.row_id && d.row_id >= d.idx) { - msg = __("Cannot refer row number greater than or equal to current row number for this Charge type"); - d.row_id = ""; - } - } - if (msg) { - frappe.validated = false; - refresh_field("taxes"); - frappe.throw(msg); - } - -} - -cur_frm.cscript.validate_inclusive_tax = function(tax) { - var actual_type_error = function() { - var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]) - frappe.throw(msg); - }; - - var on_previous_row_error = function(row_range) { - var msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included", - [tax.idx, __(tax.doctype), tax.charge_type, row_range]) - frappe.throw(msg); - }; - - if(cint(tax.included_in_print_rate)) { - if(tax.charge_type == "Actual") { - // inclusive tax cannot be of type Actual - actual_type_error(); - } else if(tax.charge_type == "On Previous Row Amount" && - !cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate) - ) { - // referred row should also be an inclusive tax - on_previous_row_error(tax.row_id); - } else if(tax.charge_type == "On Previous Row Total") { - var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), - function(t) { return cint(t.included_in_print_rate) ? null : t; }); - if(taxes_not_included.length > 0) { - // all rows above this tax should be inclusive - on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id); +erpnext.accounts.pos = { + setup: function(doctype) { + frappe.ui.form.on(doctype, { + mode_of_payment: function(frm, cdt, cdn) { + var d = locals[cdt][cdn]; + get_payment_mode_account(frm, d.mode_of_payment, function(account){ + frappe.model.set_value(cdt, cdn, 'account', account) + }) } - } else if(tax.category == "Valuation") { - frappe.throw(__("Valuation type charges can not marked as Inclusive")); - } - } -} - -if(!erpnext.taxes.flags[cur_frm.cscript.tax_table]) { - erpnext.taxes.flags[cur_frm.cscript.tax_table] = true; - - frappe.ui.form.on(cur_frm.cscript.tax_table, "row_id", function(frm, cdt, cdn) { - cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); - }); - - frappe.ui.form.on(cur_frm.cscript.tax_table, "rate", function(frm, cdt, cdn) { - cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); - }); - - frappe.ui.form.on(cur_frm.cscript.tax_table, "tax_amount", function(frm, cdt, cdn) { - cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); - }); - - frappe.ui.form.on(cur_frm.cscript.tax_table, "charge_type", function(frm, cdt, cdn) { - frm.cscript.validate_taxes_and_charges(cdt, cdn); - var open_form = frm.open_grid_row(); - if(open_form) { - erpnext.taxes.set_conditional_mandatory_rate_or_amount(open_form); - } else { - // apply in current row - erpnext.taxes.set_conditional_mandatory_rate_or_amount(frm.get_field('taxes').grid.get_row(cdn)); - } - }); - - frappe.ui.form.on(cur_frm.cscript.tax_table, "included_in_print_rate", function(frm, cdt, cdn) { - var tax = frappe.get_doc(cdt, cdn); - try { - cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); - cur_frm.cscript.validate_inclusive_tax(tax); - } catch(e) { - tax.included_in_print_rate = 0; - refresh_field("included_in_print_rate", tax.name, tax.parentfield); - throw e; - } - }); -} - -erpnext.taxes.set_conditional_mandatory_rate_or_amount = function(grid_row) { - if(grid_row) { - if(grid_row.doc.charge_type==="Actual") { - grid_row.toggle_editable("tax_amount", true); - grid_row.toggle_reqd("tax_amount", true); - grid_row.toggle_editable("rate", false); - grid_row.toggle_reqd("rate", false); - } else { - grid_row.toggle_editable("rate", true); - grid_row.toggle_reqd("rate", true); - grid_row.toggle_editable("tax_amount", false); - grid_row.toggle_reqd("tax_amount", false); + }); + }, + + get_payment_mode_account: function(frm, mode_of_payment, callback) { + if(!frm.doc.company) { + frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")}); } + + if(!mode_of_payment) { + return; + } + + return frappe.call({ + method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", + args: { + "mode_of_payment": mode_of_payment, + "company": frm.doc.company + }, + callback: function(r, rt) { + if(r.message) { + callback(r.message.account) + } + } + }); } } diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 85a2e242db..0f4a65017c 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -24,5 +24,6 @@ import "./bulk_transaction_processing"; import "./utils/crm_activities"; import "./templates/crm_activities.html"; import "./templates/crm_notes.html"; +import "./controllers/accounts.js" // import { sum } from 'frappe/public/utils/util.js' diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 5d43a07d96..2707ded5e3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -3,6 +3,9 @@ {% include 'erpnext/selling/sales_common.js' %} +erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); +erpnext.accounts.taxes.setup_tax_validations("Sales Order"); + frappe.ui.form.on("Sales Order", { setup: function(frm) { frm.custom_make_buttons = { diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index a648195933..1df797301e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -9,6 +9,10 @@ frappe.provide("erpnext.stock"); frappe.provide("erpnext.stock.delivery_note"); frappe.provide("erpnext.accounts.dimensions"); +erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); +erpnext.accounts.taxes.setup_tax_validations("Delivery Note"); + + frappe.ui.form.on("Delivery Note", { setup: function(frm) { frm.custom_make_buttons = { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 35aad78c1a..5eb7c55087 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -5,6 +5,9 @@ frappe.provide("erpnext.stock"); +erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); +erpnext.accounts.taxes.setup_tax_validations("Purchase Receipt"); + frappe.ui.form.on("Purchase Receipt", { setup: (frm) => { frm.make_methods = { From fbea61bbc60a723c07595a8ae8e30982205388df Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 18 Jul 2023 15:16:52 +0530 Subject: [PATCH 070/107] fix: Trailing opening entries in Accounts closing balance (#36175) --- .../account_closing_balance/account_closing_balance.py | 4 +--- .../period_closing_voucher/period_closing_voucher.py | 8 +++++--- erpnext/patches/v14_0/update_closing_balances.py | 7 +++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py index 9540084e09..e75af7047f 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py @@ -14,10 +14,8 @@ class AccountClosingBalance(Document): pass -def make_closing_entries(closing_entries, voucher_name): +def make_closing_entries(closing_entries, voucher_name, company, closing_date): accounting_dimensions = get_accounting_dimensions() - company = closing_entries[0].get("company") - closing_date = closing_entries[0].get("closing_date") previous_closing_entries = get_previous_closing_entries( company, closing_date, accounting_dimensions diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 641f4528c5..922722f04d 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -133,6 +133,8 @@ class PeriodClosingVoucher(AccountsController): gl_entries=gl_entries, closing_entries=closing_entries, voucher_name=self.name, + company=self.company, + closing_date=self.posting_date, queue="long", ) frappe.msgprint( @@ -140,7 +142,7 @@ class PeriodClosingVoucher(AccountsController): alert=True, ) else: - process_gl_entries(gl_entries, closing_entries, voucher_name=self.name) + process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -321,7 +323,7 @@ class PeriodClosingVoucher(AccountsController): return query.run(as_dict=1) -def process_gl_entries(gl_entries, closing_entries, voucher_name=None): +def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date): from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( make_closing_entries, ) @@ -329,7 +331,7 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name=None): try: make_gl_entries(gl_entries, merge_entries=False) - make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name) + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) frappe.db.set_value( "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed" ) diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py index 2947b98740..2c84281483 100644 --- a/erpnext/patches/v14_0/update_closing_balances.py +++ b/erpnext/patches/v14_0/update_closing_balances.py @@ -69,7 +69,6 @@ def execute(): entries = gl_entries + closing_entries - if entries: - make_closing_entries(entries, voucher_name=pcv.name) - i += 1 - company_wise_order[pcv.company].append(pcv.posting_date) + make_closing_entries(entries, pcv.name, pcv.company, pcv.posting_date) + company_wise_order[pcv.company].append(pcv.posting_date) + i += 1 From b4db5e9561abc6cfae1797bfb9e657ab3253ecd2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 17:40:49 +0530 Subject: [PATCH 071/107] fix: validate docs in closed accounting period on save (#36157) fix: validate docs in closed accounting period on save (#36157) (cherry picked from commit 5985e02574e387ef92a2bf0a9d2d1b49ad57cdd3) Co-authored-by: Anand Baburajan --- .../accounting_period/accounting_period.js | 6 +++ .../accounting_period/accounting_period.py | 43 +++++++++++++++++++ .../test_accounting_period.py | 10 +++-- erpnext/accounts/general_ledger.py | 5 +-- erpnext/controllers/queries.py | 9 ++++ erpnext/hooks.py | 33 ++++++++++---- 6 files changed, 89 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.js b/erpnext/accounts/doctype/accounting_period/accounting_period.js index e3d805a168..f17b6f9c69 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.js +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.js @@ -20,5 +20,11 @@ frappe.ui.form.on('Accounting Period', { } }); } + + frm.set_query("document_type", "closed_documents", () => { + return { + query: "erpnext.controllers.queries.get_doctypes_for_closing", + } + }); } }); diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 80c9715e8e..d5f37a6806 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -11,6 +11,10 @@ class OverlapError(frappe.ValidationError): pass +class ClosedAccountingPeriod(frappe.ValidationError): + pass + + class AccountingPeriod(Document): def validate(self): self.validate_overlap() @@ -65,3 +69,42 @@ class AccountingPeriod(Document): "closed_documents", {"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed}, ) + + +def validate_accounting_period_on_doc_save(doc, method=None): + if doc.doctype == "Bank Clearance": + return + elif doc.doctype == "Asset": + if doc.is_existing_asset: + return + else: + date = doc.available_for_use_date + elif doc.doctype == "Asset Repair": + date = doc.completion_date + else: + date = doc.posting_date + + ap = frappe.qb.DocType("Accounting Period") + cd = frappe.qb.DocType("Closed Document") + + accounting_period = ( + frappe.qb.from_(ap) + .from_(cd) + .select(ap.name) + .where( + (ap.name == cd.parent) + & (ap.company == doc.company) + & (cd.closed == 1) + & (cd.document_type == doc.doctype) + & (date >= ap.start_date) + & (date <= ap.end_date) + ) + ).run(as_dict=1) + + if accounting_period: + frappe.throw( + _("You cannot create a {0} within the closed Accounting Period {1}").format( + doc.doctype, frappe.bold(accounting_period[0]["name"]) + ), + ClosedAccountingPeriod, + ) diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 85025d190f..41d94797ad 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -6,9 +6,11 @@ import unittest import frappe from frappe.utils import add_months, nowdate -from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError +from erpnext.accounts.doctype.accounting_period.accounting_period import ( + ClosedAccountingPeriod, + OverlapError, +) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.general_ledger import ClosedAccountingPeriod test_dependencies = ["Item"] @@ -33,9 +35,9 @@ class TestAccountingPeriod(unittest.TestCase): ap1.save() doc = create_sales_invoice( - do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC" + do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC" ) - self.assertRaises(ClosedAccountingPeriod, doc.submit) + self.assertRaises(ClosedAccountingPeriod, doc.save) def tearDown(self): for d in frappe.get_all("Accounting Period"): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f1dad875fa..e9dc5fc0cc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -13,14 +13,11 @@ import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) +from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.utils import create_payment_ledger_entry -class ClosedAccountingPeriod(frappe.ValidationError): - pass - - def make_gl_entries( gl_map, cancel=False, diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 3bb11282f1..d1dcd6a109 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -822,6 +822,15 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_doctypes_for_closing(doctype, txt, searchfield, start, page_len, filters): + doctypes = frappe.get_hooks("period_closing_doctypes") + if txt: + doctypes = [d for d in doctypes if txt.lower() in d.lower()] + return [(d,) for d in set(doctypes)] + + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_tax_template(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 316d9437fb..b21f37cf62 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -285,10 +285,34 @@ standard_queries = { "Customer": "erpnext.controllers.queries.customer_query", } +period_closing_doctypes = [ + "Sales Invoice", + "Purchase Invoice", + "Journal Entry", + "Bank Clearance", + "Stock Entry", + "Dunning", + "Invoice Discounting", + "Payment Entry", + "Period Closing Voucher", + "Process Deferred Accounting", + "Asset", + "Asset Capitalization", + "Asset Repair", + "Delivery Note", + "Landed Cost Voucher", + "Purchase Receipt", + "Stock Reconciliation", + "Subcontracting Receipt", +] + doc_events = { "*": { "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", }, + tuple(period_closing_doctypes): { + "validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save", + }, "Stock Entry": { "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", @@ -464,15 +488,6 @@ advance_payment_doctypes = ["Sales Order", "Purchase Order"] invoice_doctypes = ["Sales Invoice", "Purchase Invoice"] -period_closing_doctypes = [ - "Sales Invoice", - "Purchase Invoice", - "Journal Entry", - "Bank Clearance", - "Asset", - "Stock Entry", -] - bank_reconciliation_doctypes = [ "Payment Entry", "Journal Entry", From f8d4b19cb987b267dbd18d6ef1ab52426d7f51d1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Jul 2023 17:36:38 +0530 Subject: [PATCH 072/107] fix: broken overallocation validation in payment entry In a multi term payment schedule, overallocation logic broke. Fixing it using individual term outstanding amount in references. this should work for the simple, one term payment schedule as well --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index dcd7295bae..325d0f3cda 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -226,10 +226,12 @@ class PaymentEntry(AccountsController): latest_lookup = {} for d in latest_references: d = frappe._dict(d) - latest_lookup.update({(d.voucher_type, d.voucher_no): d}) + latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d for d in self.get("references"): - latest = latest_lookup.get((d.reference_doctype, d.reference_name)) + latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get( + d.payment_term + ) # The reference has already been fully paid if not latest: @@ -251,6 +253,18 @@ class PaymentEntry(AccountsController): if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): frappe.throw(fail_message.format(d.idx)) + if d.payment_term and ( + (flt(d.allocated_amount)) > 0 + and flt(d.allocated_amount) > flt(latest.payment_term_outstanding) + ): + frappe.throw( + _( + "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" + ).format( + d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term + ) + ) + # Check for negative outstanding invoices as well if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): frappe.throw(fail_message.format(d.idx)) @@ -1589,6 +1603,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), + "payment_term_outstanding": flt(payment_term.outstanding), "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, "account": d.account, @@ -2371,6 +2386,7 @@ def get_reference_as_per_payment_terms( "due_date": doc.get("due_date"), "total_amount": grand_total, "outstanding_amount": outstanding_amount, + "payment_term_outstanding": payment_term_outstanding, "payment_term": payment_term.payment_term, "allocated_amount": payment_term_outstanding, } From ee83f94bb0ad22e487d63e41cb878ffc27f974fc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Jul 2023 09:26:39 +0530 Subject: [PATCH 073/107] refactor: payment term outstanding in party account currency --- .../doctype/payment_entry/payment_entry.py | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 325d0f3cda..e9a3b79acb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1514,7 +1514,9 @@ def get_outstanding_reference_documents(args, validate=False): accounting_dimensions=accounting_dimensions_filter, ) - outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + outstanding_invoices = split_invoices_based_on_payment_terms( + outstanding_invoices, args.get("company") + ) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -1574,8 +1576,27 @@ def get_outstanding_reference_documents(args, validate=False): return data -def split_invoices_based_on_payment_terms(outstanding_invoices): +def split_invoices_based_on_payment_terms(outstanding_invoices, company): invoice_ref_based_on_payment_terms = {} + + company_currency = ( + frappe.db.get_value("Company", company, "default_currency") if company else None + ) + exc_rates = frappe._dict() + for doctype in ["Sales Invoice", "Purchase Invoice"]: + invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] + for x in frappe.db.get_all( + doctype, + filters={"name": ["in", invoices]}, + fields=["name", "currency", "conversion_rate", "party_account_currency"], + ): + exc_rates[x.name] = frappe._dict( + conversion_rate=x.conversion_rate, + currency=x.currency, + party_account_currency=x.party_account_currency, + company_currency=company_currency, + ) + for idx, d in enumerate(outstanding_invoices): if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]: payment_term_template = frappe.db.get_value( @@ -1592,6 +1613,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): for payment_term in payment_schedule: if payment_term.outstanding > 0.1: + doc_details = exc_rates.get(payment_term.parent, None) + is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and ( + doc_details.party_account_currency != doc_details.company_currency + ) + payment_term_outstanding = flt(payment_term.outstanding) + if not is_multi_currency_acc: + payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding) + invoice_ref_based_on_payment_terms.setdefault(idx, []) invoice_ref_based_on_payment_terms[idx].append( frappe._dict( @@ -1603,7 +1632,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), - "payment_term_outstanding": flt(payment_term.outstanding), + "payment_term_outstanding": payment_term_outstanding, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, "account": d.account, From 0218f11f477a5b0d43cf59d5cda2025bf68e20cf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 19 Jul 2023 12:17:32 +0530 Subject: [PATCH 074/107] ci: dont run tests on branch https://github.com/frappe/frappe/commit/6dda420176656a1f233f8e773b74f72a72fd4978 --- .github/workflows/server-tests-mariadb.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index 9b4db49d08..2ce1125456 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -7,11 +7,9 @@ on: - '**.css' - '**.md' - '**.html' - push: - branches: [ develop ] - paths-ignore: - - '**.js' - - '**.md' + schedule: + # Run everday at midnight UTC / 5:30 IST + - cron: "0 0 * * *" workflow_dispatch: inputs: user: From 3759a41b8358302ff8d33857a0461cc2da3d06d8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Jul 2023 13:17:12 +0530 Subject: [PATCH 075/107] fix: Default fiscal year in accounting, buying and sellingcharts --- .../dashboard_chart/budget_variance/budget_variance.json | 5 +++-- .../dashboard_chart/profit_and_loss/profit_and_loss.json | 5 +++-- .../purchase_order_trends/purchase_order_trends.json | 7 ++++--- .../dashboard_chart/top_suppliers/top_suppliers.json | 5 +++-- erpnext/public/js/utils.js | 4 ++++ .../sales_order_trends/sales_order_trends.json | 7 ++++--- .../dashboard_chart/top_customers/top_customers.json | 5 +++-- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json index 8631d3dc2a..4883106227 100644 --- a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json +++ b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json @@ -4,18 +4,19 @@ "creation": "2020-07-17 11:25:34.593061", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 12:24:49.144210", + "modified": "2023-07-19 13:13:13.307073", "modified_by": "Administrator", "module": "Accounts", "name": "Budget Variance", "number_of_groups": 0, "owner": "Administrator", "report_name": "Budget Variance Report", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json index 3fa995bbe1..25caa44769 100644 --- a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json +++ b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json @@ -4,18 +4,19 @@ "creation": "2020-07-17 11:25:34.448572", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 12:33:48.888943", + "modified": "2023-07-19 13:08:56.470390", "modified_by": "Administrator", "module": "Accounts", "name": "Profit and Loss", "number_of_groups": 0, "owner": "Administrator", "report_name": "Profit and Loss Statement", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json index 6452ed2139..751796bbbb 100644 --- a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json +++ b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json @@ -5,18 +5,19 @@ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}", - "idx": 0, + "idx": 1, "is_public": 1, "is_standard": 1, - "modified": "2020-07-21 16:13:25.092287", + "modified": "2023-07-19 13:06:42.937941", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Trends", "number_of_groups": 0, "owner": "Administrator", "report_name": "Purchase Order Trends", + "roles": [], "timeseries": 0, "type": "Line", "use_report_chart": 1, diff --git a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json index 6f7da8ea87..f6b9717539 100644 --- a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json +++ b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json @@ -4,18 +4,19 @@ "creation": "2020-07-20 21:01:02.329519", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 12:43:40.829652", + "modified": "2023-07-19 13:07:41.753556", "modified_by": "Administrator", "module": "Buying", "name": "Top Suppliers", "number_of_groups": 0, "owner": "Administrator", "report_name": "Purchase Receipt Trends", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 497f8d2674..cc03eca95d 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -401,6 +401,10 @@ $.extend(erpnext.utils, { }, get_fiscal_year: function(date) { + if(!date) { + date = frappe.datetime.get_today(); + } + let fiscal_year = ''; frappe.call({ method: "erpnext.accounts.utils.get_fiscal_year", diff --git a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json index 914d915d94..2f668a865d 100644 --- a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json +++ b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json @@ -5,18 +5,19 @@ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"based_on\":\"Item\"}", - "idx": 0, + "idx": 1, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 16:24:45.726270", + "modified": "2023-07-19 13:09:45.341791", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Trends", "number_of_groups": 0, "owner": "Administrator", "report_name": "Sales Order Trends", + "roles": [], "timeseries": 0, "type": "Line", "use_report_chart": 1, diff --git a/erpnext/selling/dashboard_chart/top_customers/top_customers.json b/erpnext/selling/dashboard_chart/top_customers/top_customers.json index 59a2ba37dd..2972980967 100644 --- a/erpnext/selling/dashboard_chart/top_customers/top_customers.json +++ b/erpnext/selling/dashboard_chart/top_customers/top_customers.json @@ -5,18 +5,19 @@ "custom_options": "", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Yearly\",\"based_on\":\"Customer\"}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 17:03:10.320147", + "modified": "2023-07-19 13:14:20.151502", "modified_by": "Administrator", "module": "Selling", "name": "Top Customers", "number_of_groups": 0, "owner": "Administrator", "report_name": "Delivery Note Trends", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, From e7e3853f819d6e50692e779da9ca90a0038c5564 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 20 Jul 2023 09:08:55 +0530 Subject: [PATCH 076/107] test: overallocation validation in payment entry --- .../payment_entry/test_payment_entry.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 70cc4b3d34..1c2d821300 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1061,6 +1061,26 @@ class TestPaymentEntry(FrappeTestCase): } self.assertDictEqual(ref_details, expected_response) + def test_overallocation_validation_on_payment_terms(self): + si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + create_payment_terms_template() + si.payment_terms_template = "Test Receivable Template" + si.save().submit() + + si.reload() + si.payment_schedule[0].payment_amount + + pe = get_payment_entry(si.doctype, si.name).save() + # Allocated amount should be according to the payment schedule + for idx, schedule in enumerate(si.payment_schedule): + with self.subTest(idx=idx): + self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.paid_amount = 400 + pe.references[0].allocated_amount = 200 + pe.references[1].allocated_amount = 200 + + self.assertRaises(frappe.ValidationError, pe.save) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 16498627cec1e416fcc9a911b11ca522268eb622 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Jul 2023 12:55:10 +0530 Subject: [PATCH 077/107] fix: made item or warehouse filter mandatory --- .../batch_wise_balance_history.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index c07287437a..e7d3e208d1 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -10,11 +10,18 @@ from pypika import functions as fn from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter +SLE_COUNT_LIMIT = 10_000 + def execute(filters=None): if not filters: filters = {} + sle_count = frappe.db.count("Stock Ledger Entry", {"is_cancelled": 0}) + + if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"): + frappe.throw(_("Please select either the Item or Warehouse filter to generate the report.")) + if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) From c21fd45883d3b2a6d2ba32c5f91d5151c8df0650 Mon Sep 17 00:00:00 2001 From: MohsinAli Date: Thu, 20 Jul 2023 13:51:17 +0530 Subject: [PATCH 078/107] fix: Ambiguous column error while submitting stock entry Stock Entry Type=Manufacture request.js:457 Traceback (most recent call last): File "apps/frappe/frappe/app.py", line 94, in application response = frappe.api.handle() File "apps/frappe/frappe/api.py", line 54, in handle return frappe.handler.handle() File "apps/frappe/frappe/handler.py", line 47, in handle data = execute_cmd(cmd) File "apps/frappe/frappe/handler.py", line 85, in execute_cmd return frappe.call(method, **frappe.form_dict) File "apps/frappe/frappe/__init__.py", line 1610, in call return fn(*args, **newargs) File "apps/frappe/frappe/desk/form/save.py", line 28, in savedocs doc.save() File "apps/frappe/frappe/model/document.py", line 305, in save return self._save(*args, **kwargs) File "apps/frappe/frappe/model/document.py", line 327, in _save return self.insert() File "apps/frappe/frappe/model/document.py", line 259, in insert self.run_before_save_methods() File "apps/frappe/frappe/model/document.py", line 1045, in run_before_save_methods self.run_method("validate") File "apps/frappe/frappe/model/document.py", line 914, in run_method out = Document.hook(fn)(self, *args, **kwargs) File "apps/frappe/frappe/model/document.py", line 1264, in composer return composed(self, method, *args, **kwargs) File "apps/frappe/frappe/model/document.py", line 1246, in runner add_to_return_value(self, fn(self, *args, **kwargs)) File "apps/frappe/frappe/model/document.py", line 911, in fn return method_object(*args, **kwargs) File "apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py", line 122, in validate self.validate_qty() File "apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py", line 433, in validate_qty transferred_materials = frappe.db.sql( File "apps/frappe/frappe/database/database.py", line 220, in sql self._cursor.execute(query, values) File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 158, in execute result = self._query(query) File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 325, in _query conn.query(q) File "env/lib/python3.10/site-packages/pymysql/connections.py", line 549, in query self._affected_rows = self._read_query_result(unbuffered=unbuffered) File "env/lib/python3.10/site-packages/pymysql/connections.py", line 779, in _read_query_result result.read() File "env/lib/python3.10/site-packages/pymysql/connections.py", line 1157, in read first_packet = self.connection._read_packet() File "env/lib/python3.10/site-packages/pymysql/connections.py", line 729, in _read_packet packet.raise_for_error() File "env/lib/python3.10/site-packages/pymysql/protocol.py", line 221, in raise_for_error err.raise_mysql_exception(self._data) File "env/lib/python3.10/site-packages/pymysql/err.py", line 143, in raise_mysql_exception raise errorclass(errno, errval) pymysql.err.OperationalError: (1052, "Column 'qty' in field list is ambiguous") --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d9b5503b50..0059a3f432 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -420,7 +420,7 @@ class StockEntry(StockController): transferred_materials = frappe.db.sql( """ select - sum(qty) as qty + sum(sed.qty) as qty from `tabStock Entry` se,`tabStock Entry Detail` sed where se.name = sed.parent and se.docstatus=1 and From fd58bbff6bbed96367544f0ab0be9d992fafef56 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Jul 2023 17:51:54 +0530 Subject: [PATCH 079/107] fix: Trial Balance report considering cancelled entries --- .../period_closing_voucher.py | 45 +++++++++---------- .../report/trial_balance/trial_balance.py | 3 ++ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 922722f04d..49472484ef 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -126,23 +126,22 @@ class PeriodClosingVoucher(AccountsController): def make_gl_entries(self, get_opening_entries=False): gl_entries = self.get_gl_entries() closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries) - if gl_entries: - if len(gl_entries) > 5000: - frappe.enqueue( - process_gl_entries, - gl_entries=gl_entries, - closing_entries=closing_entries, - voucher_name=self.name, - company=self.company, - closing_date=self.posting_date, - queue="long", - ) - frappe.msgprint( - _("The GL Entries will be processed in the background, it can take a few minutes."), - alert=True, - ) - else: - process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) + if len(gl_entries) > 5000: + frappe.enqueue( + process_gl_entries, + gl_entries=gl_entries, + closing_entries=closing_entries, + voucher_name=self.name, + company=self.company, + closing_date=self.posting_date, + queue="long", + ) + frappe.msgprint( + _("The GL Entries will be processed in the background, it can take a few minutes."), + alert=True, + ) + else: + process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -330,17 +329,15 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi from erpnext.accounts.general_ledger import make_gl_entries try: - make_gl_entries(gl_entries, merge_entries=False) + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) - frappe.db.set_value( - "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed" - ) + frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) - frappe.db.set_value( - "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed" - ) + frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") def make_reverse_gl_entries(voucher_type, voucher_no): diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 39917f90c9..599c8a312a 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -231,6 +231,9 @@ def get_opening_balance( (closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes") ) + if doctype == "GL Entry": + opening_balance = opening_balance.where(closing_balance.is_cancelled == 0) + if ( not filters.show_unclosed_fy_pl_balances and report_type == "Profit and Loss" From aeae8d646a1efdc88866d7f08518d57bcf78b1b6 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jul 2023 19:00:39 +0530 Subject: [PATCH 080/107] fix: Patch dunnings made after accounts were frozen - Consider "Accounts Frozen Until" and Period Closing Voucher --- .../patches/v14_0/single_to_multi_dunning.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py index 7a8e591798..3567522eb3 100644 --- a/erpnext/patches/v14_0/single_to_multi_dunning.py +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -7,7 +7,15 @@ def execute(): frappe.reload_doc("accounts", "doctype", "overdue_payment") frappe.reload_doc("accounts", "doctype", "dunning") - all_dunnings = frappe.get_all("Dunning", filters={"docstatus": ("!=", 2)}, pluck="name") + filters = {"docstatus": ("!=", 2)} + + can_edit_accounts_after = get_accounts_closing_date() + if can_edit_accounts_after: + # Get dunnings after the date when accounts were frozen/closed + filters["posting_date"] = (">", can_edit_accounts_after) + + all_dunnings = frappe.get_all("Dunning", filters=filters, pluck="name") + for dunning_name in all_dunnings: dunning = frappe.get_doc("Dunning", dunning_name) if not dunning.sales_invoice: @@ -47,3 +55,22 @@ def execute(): # so we reverse previous GL Entries that recorded the dunning amount at # time of submission of the Dunning. make_reverse_gl_entries(voucher_type="Dunning", voucher_no=dunning.name) + + +def get_accounts_closing_date(): + """Get the date when accounts were frozen/closed""" + accounts_frozen_till = frappe.db.get_single_value( + "Accounts Settings", "acc_frozen_upto" + ) # always returns datetime.date + + period_closing_date = frappe.db.get_value( + "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + ) + + # Set most recent frozen/closing date as filter + if accounts_frozen_till and period_closing_date: + can_edit_accounts_after = max(accounts_frozen_till, period_closing_date) + else: + can_edit_accounts_after = accounts_frozen_till or period_closing_date + + return can_edit_accounts_after From 17ff395f9a230a8ffbb3c821fb5c357d9261daba Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jul 2023 20:54:43 +0530 Subject: [PATCH 081/107] fix: Reverse GL entries only for submitted Dunnings --- erpnext/patches/v14_0/single_to_multi_dunning.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py index 3567522eb3..3b01871d43 100644 --- a/erpnext/patches/v14_0/single_to_multi_dunning.py +++ b/erpnext/patches/v14_0/single_to_multi_dunning.py @@ -7,6 +7,7 @@ def execute(): frappe.reload_doc("accounts", "doctype", "overdue_payment") frappe.reload_doc("accounts", "doctype", "dunning") + # Migrate schema of all uncancelled dunnings filters = {"docstatus": ("!=", 2)} can_edit_accounts_after = get_accounts_closing_date() @@ -49,7 +50,8 @@ def execute(): dunning.flags.ignore_validate_update_after_submit = True dunning.save() - if dunning.status != "Resolved": + # Reverse entries only if dunning is submitted and not resolved + if dunning.docstatus == 1 and dunning.status != "Resolved": # With the new logic, dunning amount gets recorded as additional income # at time of payment. We don't want to record the dunning amount twice, # so we reverse previous GL Entries that recorded the dunning amount at From 1c5c310f5a16fb87e5c741156ad7dc567cbd8400 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 21 Jul 2023 13:37:48 +0530 Subject: [PATCH 082/107] fix: fetch acc dimension fieldname --- .../profitability_analysis.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index c05aa94457..3d6e9b5428 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.utils import cstr, flt +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.report.financial_statements import ( filter_accounts, filter_out_zero_value_rows, @@ -19,7 +20,9 @@ def execute(filters=None): if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"): frappe.throw(_("Select Accounting Dimension.")) - based_on = filters.based_on.replace(" ", "_").lower() + based_on = ( + filters.based_on if filters.based_on != "Accounting Dimension" else filters.accounting_dimension + ) validate_filters(filters) accounts = get_accounts_data(based_on, filters.get("company")) data = get_data(accounts, filters, based_on) @@ -28,17 +31,15 @@ def execute(filters=None): def get_accounts_data(based_on, company): - if based_on == "cost_center": + if based_on == "Cost Center": return frappe.db.sql( """select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt from `tabCost Center` where company=%s order by name""", company, as_dict=True, ) - elif based_on == "project": + elif based_on == "Project": return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name") - elif based_on == "accounting_dimension": - return frappe.get_all("Accounting Dimension", fields=["name"], order_by="name") else: filters = {} doctype = frappe.unscrub(based_on) @@ -58,13 +59,17 @@ def get_data(accounts, filters, based_on): gl_entries_by_account = {} + accounting_dimensions = get_dimensions(with_cost_center_and_project=True)[0] + fieldname = "" + for dimension in accounting_dimensions: + if dimension["document_type"] == based_on: + fieldname = dimension["fieldname"] + set_gl_entries_by_account( filters.get("company"), filters.get("from_date"), filters.get("to_date"), - based_on - if based_on != "accounting_dimension" - else filters.accounting_dimension.replace(" ", "_").lower(), + fieldname, gl_entries_by_account, ignore_closing_entries=not flt(filters.get("with_period_closing_entry")), ) From 7a7d32db817ab6619168ad3582abe23c197d6419 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 21 Jul 2023 16:03:17 +0530 Subject: [PATCH 083/107] fix: FY in naming series variable for orders --- erpnext/accounts/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 4b54483bc0..e354663151 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1112,7 +1112,8 @@ def get_autoname_with_number(number_value, doc_title, company): def parse_naming_series_variable(doc, variable): if variable == "FY": - return get_fiscal_year(date=doc.get("posting_date"), company=doc.get("company"))[0] + date = doc.get("posting_date") or doc.get("transaction_date") or getdate() + return get_fiscal_year(date=date, company=doc.get("company"))[0] @frappe.whitelist() From b84deec601b79679137377c6500d7ca3af9202ac Mon Sep 17 00:00:00 2001 From: DaizyModi Date: Fri, 21 Jul 2023 17:26:35 +0530 Subject: [PATCH 084/107] fix: Correct Tax Breakup for different tax rates for same hsn code --- erpnext/controllers/taxes_and_totals.py | 34 +++++++++++++------ .../includes/itemised_tax_breakup.html | 8 ++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 4661c5ca7e..d9b8acb8ed 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -955,9 +955,9 @@ def get_itemised_tax_breakup_html(doc): headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) # get tax breakup data - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) + itemised_tax_data = get_itemised_tax_breakup_data(doc) - get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) + get_rounded_tax_amount(itemised_tax_data, doc.precision("tax_amount", "taxes"), tax_accounts) update_itemised_tax_data(doc) frappe.flags.company = None @@ -966,8 +966,7 @@ def get_itemised_tax_breakup_html(doc): "templates/includes/itemised_tax_breakup.html", dict( headers=headers, - itemised_tax=itemised_tax, - itemised_taxable_amount=itemised_taxable_amount, + itemised_tax_data=itemised_tax_data, tax_accounts=tax_accounts, doc=doc, ), @@ -1000,12 +999,24 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts): @erpnext.allow_regional -def get_itemised_tax_breakup_data(doc): - itemised_tax = get_itemised_tax(doc.taxes) +def get_itemised_tax_breakup_data(doc, with_tax_account=False): + return _get_itemised_tax_breakup_data(doc, with_tax_account=False) + + +def _get_itemised_tax_breakup_data(doc, with_tax_account=False): + itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=with_tax_account) itemised_taxable_amount = get_itemised_taxable_amount(doc.items) - return itemised_tax, itemised_taxable_amount + itemised_tax_data = [] + for item_code, taxes in itemised_tax.items(): + for _item_code, taxable_amount in itemised_taxable_amount.items(): + if item_code == _item_code: + itemised_tax_data.append( + frappe._dict({"item": item_code, "taxable_amount": taxable_amount, **taxes}) + ) + + return itemised_tax_data def get_itemised_tax(taxes, with_tax_account=False): @@ -1048,11 +1059,12 @@ def get_itemised_taxable_amount(items): return itemised_taxable_amount -def get_rounded_tax_amount(itemised_tax, precision): +def get_rounded_tax_amount(itemised_tax, precision, tax_accounts): # Rounding based on tax_amount precision - for taxes in itemised_tax.values(): - for tax_account in taxes: - taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) + for _itemised_tax in itemised_tax: + for key, value in _itemised_tax.items(): + if key in tax_accounts: + value["tax_amount"] = flt(value["tax_amount"], precision) class init_landed_taxes_and_totals(object): diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html index fbc80de7d0..89d4373036 100644 --- a/erpnext/templates/includes/itemised_tax_breakup.html +++ b/erpnext/templates/includes/itemised_tax_breakup.html @@ -12,14 +12,14 @@
- {% for item, taxes in itemised_tax.items() %} + {% for taxes in itemised_tax_data %} - + {% for tax_account in tax_accounts %} From 653117c2a93d9fca2a9f421a8adba21f261f6e64 Mon Sep 17 00:00:00 2001 From: DaizyModi Date: Fri, 21 Jul 2023 17:52:54 +0530 Subject: [PATCH 085/107] test: fix test case for itemised tax breakup --- .../sales_invoice/test_sales_invoice.py | 22 ++++++++++++------- erpnext/controllers/taxes_and_totals.py | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0280c3590c..41e55546a8 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1900,16 +1900,22 @@ class TestSalesInvoice(unittest.TestCase): si = self.create_si_to_test_tax_breakup() - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) + itemised_tax_data = get_itemised_tax_breakup_data(si) - expected_itemised_tax = { - "_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}}, - "_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}}, - } - expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0} + expected_itemised_tax = [ + { + "item": "_Test Item", + "taxable_amount": 10000.0, + "Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}, + }, + { + "item": "_Test Item 2", + "taxable_amount": 5000.0, + "Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}, + }, + ] - self.assertEqual(itemised_tax, expected_itemised_tax) - self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) + self.assertEqual(itemised_tax_data, expected_itemised_tax) frappe.flags.country = None diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index d9b8acb8ed..04d5e1c69a 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -999,8 +999,8 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts): @erpnext.allow_regional -def get_itemised_tax_breakup_data(doc, with_tax_account=False): - return _get_itemised_tax_breakup_data(doc, with_tax_account=False) +def get_itemised_tax_breakup_data(doc): + return _get_itemised_tax_breakup_data(doc) def _get_itemised_tax_breakup_data(doc, with_tax_account=False): From 6b4a81ee482d4a2c6ce32ab69a3b42cfd5496b60 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 20 Jul 2023 21:19:29 +0530 Subject: [PATCH 086/107] chore: test more scenarios --- .../payment_entry/test_payment_entry.py | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 1c2d821300..2fe1f61060 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1061,25 +1061,94 @@ class TestPaymentEntry(FrappeTestCase): } self.assertDictEqual(ref_details, expected_response) + @change_settings( + "Accounts Settings", + {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + ) def test_overallocation_validation_on_payment_terms(self): - si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + """ + Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown. + + """ create_payment_terms_template() - si.payment_terms_template = "Test Receivable Template" - si.save().submit() - si.reload() - si.payment_schedule[0].payment_amount + # Validate allocation on base/company currency + si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200) + si1.payment_terms_template = "Test Receivable Template" + si1.save().submit() - pe = get_payment_entry(si.doctype, si.name).save() + si1.reload() + pe = get_payment_entry(si1.doctype, si1.name).save() # Allocated amount should be according to the payment schedule - for idx, schedule in enumerate(si.payment_schedule): + for idx, schedule in enumerate(si1.payment_schedule): with self.subTest(idx=idx): self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.save() + + # Overallocation validation should trigger pe.paid_amount = 400 pe.references[0].allocated_amount = 200 pe.references[1].allocated_amount = 200 - self.assertRaises(frappe.ValidationError, pe.save) + pe.delete() + si1.cancel() + si1.delete() + + # Validate allocation on foreign currency + si2 = create_sales_invoice( + customer="_Test Customer USD", + debit_to="_Test Receivable USD - _TC", + currency="USD", + conversion_rate=80, + do_not_save=1, + ) + si2.payment_terms_template = "Test Receivable Template" + si2.save().submit() + + si2.reload() + pe = get_payment_entry(si2.doctype, si2.name).save() + # Allocated amount should be according to the payment schedule + for idx, schedule in enumerate(si2.payment_schedule): + with self.subTest(idx=idx): + self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.save() + + # Overallocation validation should trigger + pe.paid_amount = 200 + pe.references[0].allocated_amount = 100 + pe.references[1].allocated_amount = 100 + self.assertRaises(frappe.ValidationError, pe.save) + pe.delete() + si2.cancel() + si2.delete() + + # Validate allocation in base/company currency on a foreign currency document + # when invoice is made is foreign currency, but posted to base/company currency account + si3 = create_sales_invoice( + customer="_Test Customer USD", + currency="USD", + conversion_rate=80, + do_not_save=1, + ) + si3.payment_terms_template = "Test Receivable Template" + si3.save().submit() + + si3.reload() + pe = get_payment_entry(si3.doctype, si3.name).save() + # Allocated amount should be according to the payment schedule + for idx, schedule in enumerate(si3.payment_schedule): + with self.subTest(idx=idx): + self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.save() + + # Overallocation validation should trigger + pe.paid_amount = 400 + pe.references[0].allocated_amount = 200 + pe.references[1].allocated_amount = 200 + self.assertRaises(frappe.ValidationError, pe.save) + # pe.delete() + # si3.cancel() + # si3.delete() def create_payment_entry(**args): From 5b37919574ac6b3454c99faffe1b42ceaca0f74e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 08:15:03 +0530 Subject: [PATCH 087/107] chore: use flt for currency --- .../payment_entry/test_payment_entry.py | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 2fe1f61060..87459dc0a7 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1070,6 +1070,7 @@ class TestPaymentEntry(FrappeTestCase): Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown. """ + customer = create_customer() create_payment_terms_template() # Validate allocation on base/company currency @@ -1082,7 +1083,7 @@ class TestPaymentEntry(FrappeTestCase): # Allocated amount should be according to the payment schedule for idx, schedule in enumerate(si1.payment_schedule): with self.subTest(idx=idx): - self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount)) pe.save() # Overallocation validation should trigger @@ -1110,7 +1111,7 @@ class TestPaymentEntry(FrappeTestCase): # Allocated amount should be according to the payment schedule for idx, schedule in enumerate(si2.payment_schedule): with self.subTest(idx=idx): - self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount)) pe.save() # Overallocation validation should trigger @@ -1123,9 +1124,9 @@ class TestPaymentEntry(FrappeTestCase): si2.delete() # Validate allocation in base/company currency on a foreign currency document - # when invoice is made is foreign currency, but posted to base/company currency account + # when invoice is made is foreign currency, but posted to base/company currency debtors account si3 = create_sales_invoice( - customer="_Test Customer USD", + customer=customer, currency="USD", conversion_rate=80, do_not_save=1, @@ -1138,17 +1139,17 @@ class TestPaymentEntry(FrappeTestCase): # Allocated amount should be according to the payment schedule for idx, schedule in enumerate(si3.payment_schedule): with self.subTest(idx=idx): - self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + self.assertEqual(flt(schedule.base_payment_amount), flt(pe.references[idx].allocated_amount)) pe.save() # Overallocation validation should trigger - pe.paid_amount = 400 - pe.references[0].allocated_amount = 200 - pe.references[1].allocated_amount = 200 + pe.paid_amount = 16000 + pe.references[0].allocated_amount = 8000 + pe.references[1].allocated_amount = 8000 self.assertRaises(frappe.ValidationError, pe.save) - # pe.delete() - # si3.cancel() - # si3.delete() + pe.delete() + si3.cancel() + si3.delete() def create_payment_entry(**args): @@ -1239,3 +1240,17 @@ def create_payment_terms_template_with_discount( def create_payment_term(name): if not frappe.db.exists("Payment Term", name): frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert() + + +def create_customer(name="_Test Customer 2 USD", currency="USD"): + customer = None + if frappe.db.exists("Customer", name): + customer = name + else: + customer = frappe.new_doc("Customer") + customer.customer_name = name + customer.default_currency = currency + customer.type = "Individual" + customer.save() + customer = customer.name + return customer From 8f9ef4ef5b411d4debe460b607ccb78c6490462a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 09:16:03 +0530 Subject: [PATCH 088/107] chore: validation on multi-currency tran on company curtency account --- .../accounts/doctype/payment_entry/test_payment_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 87459dc0a7..17e82abe42 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1136,10 +1136,11 @@ class TestPaymentEntry(FrappeTestCase): si3.reload() pe = get_payment_entry(si3.doctype, si3.name).save() - # Allocated amount should be according to the payment schedule - for idx, schedule in enumerate(si3.payment_schedule): + # Allocated amount should be equal to payment term outstanding + self.assertEqual(len(pe.references), 2) + for idx, ref in enumerate(pe.references): with self.subTest(idx=idx): - self.assertEqual(flt(schedule.base_payment_amount), flt(pe.references[idx].allocated_amount)) + self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount) pe.save() # Overallocation validation should trigger From 93246043ec092268d9b011e024bedf8b008993a1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 10:01:59 +0530 Subject: [PATCH 089/107] chore(test): enable multi-currency party for testing --- .../accounts/doctype/payment_entry/test_payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 17e82abe42..c6e93f3f7a 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1063,7 +1063,11 @@ class TestPaymentEntry(FrappeTestCase): @change_settings( "Accounts Settings", - {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + { + "unlink_payment_on_cancellation_of_invoice": 1, + "delete_linked_ledger_entries": 1, + "allow_multi_currency_invoices_against_single_party_account": 1, + }, ) def test_overallocation_validation_on_payment_terms(self): """ From 0b1e78e12779d26a836e81e238aabfdd12245fac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 22 Jul 2023 21:19:48 +0530 Subject: [PATCH 090/107] chore: Add landed cost taxes and charges common in bundles --- erpnext/public/js/erpnext.bundle.js | 1 + .../utils/landed_taxes_and_charges_common.js | 62 +++++++++++++++++++ .../landed_cost_voucher.js | 3 +- .../stock/doctype/stock_entry/stock_entry.js | 13 ++-- .../stock/landed_taxes_and_charges_common.js | 61 ------------------ .../subcontracting_order.js | 2 +- .../subcontracting_receipt.js | 2 +- erpnext/www/book_appointment/index.js | 2 +- 8 files changed, 74 insertions(+), 72 deletions(-) create mode 100644 erpnext/public/js/utils/landed_taxes_and_charges_common.js delete mode 100644 erpnext/stock/landed_taxes_and_charges_common.js diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 0f4a65017c..3d5025dd25 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -25,5 +25,6 @@ import "./utils/crm_activities"; import "./templates/crm_activities.html"; import "./templates/crm_notes.html"; import "./controllers/accounts.js" +import "./utils/landed_taxes_and_charges_common.js"; // import { sum } from 'frappe/public/utils/util.js' diff --git a/erpnext/public/js/utils/landed_taxes_and_charges_common.js b/erpnext/public/js/utils/landed_taxes_and_charges_common.js new file mode 100644 index 0000000000..c71f77d45e --- /dev/null +++ b/erpnext/public/js/utils/landed_taxes_and_charges_common.js @@ -0,0 +1,62 @@ + +erpnext.landed_cost_taxes_and_charges = { + setup_triggers: function(doctype) { + frappe.ui.form.on(doctype, { + refresh: function(frm) { + let tax_field = frm.doc.doctype == 'Landed Cost Voucher' ? 'taxes' : 'additional_costs'; + frm.set_query("expense_account", tax_field, function() { + return { + filters: { + "account_type": ['in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]], + "company": frm.doc.company + } + }; + }); + }, + + set_account_currency: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.expense_account) { + frappe.db.get_value('Account', row.expense_account, 'account_currency', function(value) { + frappe.model.set_value(cdt, cdn, "account_currency", value.account_currency); + frm.events.set_exchange_rate(frm, cdt, cdn); + }); + } + }, + + set_exchange_rate: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + + if (row.account_currency == company_currency) { + row.exchange_rate = 1; + frm.set_df_property('taxes', 'hidden', 1, row.name, 'exchange_rate'); + } else if (!row.exchange_rate || row.exchange_rate == 1) { + frm.set_df_property('taxes', 'hidden', 0, row.name, 'exchange_rate'); + frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", + args: { + posting_date: frm.doc.posting_date, + account: row.expense_account, + account_currency: row.account_currency, + company: frm.doc.company + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); + } + } + }); + } + + frm.refresh_field('taxes'); + }, + + set_base_amount: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, "base_amount", + flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row))); + } + }); + } +} diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 9c1a809f4d..8215efcad3 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -1,10 +1,9 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; - frappe.provide("erpnext.stock"); +erpnext.landed_cost_taxes_and_charges.setup_triggers("Landed Cost Voucher"); erpnext.stock.LandedCostVoucher = class LandedCostVoucher extends erpnext.stock.StockController { setup() { var me = this; diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 403e04ae60..e9b7949eb3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -3,7 +3,7 @@ frappe.provide("erpnext.stock"); frappe.provide("erpnext.accounts.dimensions"); -{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; +erpnext.landed_cost_taxes_and_charges.setup_triggers("Stock Entry"); frappe.ui.form.on('Stock Entry', { setup: function(frm) { @@ -56,7 +56,7 @@ frappe.ui.form.on('Stock Entry', { frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => { if (r.sample_retention_warehouse) { - var filters = [ + let filters = [ ["Warehouse", 'company', '=', frm.doc.company], ["Warehouse", "is_group", "=",0], ['Warehouse', 'name', '!=', r.sample_retention_warehouse] @@ -75,17 +75,19 @@ frappe.ui.form.on('Stock Entry', { }); frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { - var item = locals[cdt][cdn]; + let item = locals[cdt][cdn]; + let filters = {}; + if(!item.item_code) { frappe.throw(__("Please enter Item Code to get Batch Number")); } else { if (in_list(["Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"], doc.purpose)) { - var filters = { + filters = { 'item_code': item.item_code, 'posting_date': frm.doc.posting_date || frappe.datetime.nowdate() } } else { - var filters = { + filters = { 'item_code': item.item_code } } @@ -686,7 +688,6 @@ frappe.ui.form.on('Stock Entry', { }, process_loss_percentage(frm) { - debugger if (frm.doc.process_loss_percentage) { frm.doc.process_loss_qty = flt((frm.doc.fg_completed_qty * frm.doc.process_loss_percentage) / 100 , precision("process_loss_qty", frm.doc)); refresh_field("process_loss_qty"); diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js deleted file mode 100644 index 1d76a3d95a..0000000000 --- a/erpnext/stock/landed_taxes_and_charges_common.js +++ /dev/null @@ -1,61 +0,0 @@ -let document_list = ['Landed Cost Voucher', 'Stock Entry', 'Subcontracting Order', 'Subcontracting Receipt']; - -document_list.forEach((doctype) => { - frappe.ui.form.on(doctype, { - refresh: function(frm) { - let tax_field = frm.doc.doctype == 'Landed Cost Voucher' ? 'taxes' : 'additional_costs'; - frm.set_query("expense_account", tax_field, function() { - return { - filters: { - "account_type": ['in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]], - "company": frm.doc.company - } - }; - }); - }, - - set_account_currency: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.expense_account) { - frappe.db.get_value('Account', row.expense_account, 'account_currency', function(value) { - frappe.model.set_value(cdt, cdn, "account_currency", value.account_currency); - frm.events.set_exchange_rate(frm, cdt, cdn); - }); - } - }, - - set_exchange_rate: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - - if (row.account_currency == company_currency) { - row.exchange_rate = 1; - frm.set_df_property('taxes', 'hidden', 1, row.name, 'exchange_rate'); - } else if (!row.exchange_rate || row.exchange_rate == 1) { - frm.set_df_property('taxes', 'hidden', 0, row.name, 'exchange_rate'); - frappe.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", - args: { - posting_date: frm.doc.posting_date, - account: row.expense_account, - account_currency: row.account_currency, - company: frm.doc.company - }, - callback: function(r) { - if (r.message) { - frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); - } - } - }); - } - - frm.refresh_field('taxes'); - }, - - set_base_amount: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, "base_amount", - flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row))); - } - }); -}); diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 15a2ac9091..f2b395ac10 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -3,7 +3,7 @@ frappe.provide('erpnext.buying'); -{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; +erpnext.landed_cost_taxes_and_charges.setup_triggers("Subcontracting Order"); frappe.ui.form.on('Subcontracting Order', { setup: (frm) => { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 5ee1f7b716..94a2589b98 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -3,7 +3,7 @@ frappe.provide('erpnext.buying'); -{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; +erpnext.landed_cost_taxes_and_charges.setup_triggers("Subcontracting Receipt"); frappe.ui.form.on('Subcontracting Receipt', { setup: (frm) => { diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js index d02cdad4cf..71a34d4e13 100644 --- a/erpnext/www/book_appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -243,7 +243,7 @@ async function submit() { } function get_form_data() { - contact = {}; + let contact = {}; let inputs = ['name', 'skype', 'number', 'notes', 'email']; inputs.forEach((id) => contact[id] = document.getElementById(`customer_${id}`).value) return contact From 4077254b019943ac9d9f68befa156fc7acbd48cc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 22 Jul 2023 23:07:18 +0530 Subject: [PATCH 091/107] chore: resolve undefined variables issue --- .pre-commit-config.yaml | 3 ++- erpnext/accounts/doctype/cost_center/cost_center.js | 6 +++--- erpnext/accounts/doctype/payment_term/payment_term.js | 2 +- .../buying/doctype/supplier_scorecard/supplier_scorecard.js | 4 +--- .../doctype/supplier_scorecard/supplier_scorecard_list.js | 3 +-- .../supplier_scorecard_criteria.js | 2 -- erpnext/public/js/controllers/accounts.js | 2 +- erpnext/public/js/utils.js | 6 ++++-- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- erpnext/stock/dashboard/item_dashboard.js | 2 -- erpnext/stock/doctype/delivery_note/delivery_note_list.js | 6 +++--- 11 files changed, 17 insertions(+), 21 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93d07485e6..b01b56a856 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,8 @@ repos: cypress/.*| .*node_modules.*| .*boilerplate.*| - erpnext/public/js/controllers/.* + erpnext/public/js/controllers/.*| + erpnext/templates/pages/order.js )$ - repo: https://github.com/PyCQA/flake8 diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js index 632fab0197..c427cc8b87 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.js +++ b/erpnext/accounts/doctype/cost_center/cost_center.js @@ -70,7 +70,7 @@ frappe.ui.form.on('Cost Center', { } ], primary_action: function() { - var data = d.get_values(); + let data = d.get_values(); if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) { d.hide(); return; @@ -91,8 +91,8 @@ frappe.ui.form.on('Cost Center', { if(r.message) { frappe.set_route("Form", "Cost Center", r.message); } else { - me.frm.set_value("cost_center_name", data.cost_center_name); - me.frm.set_value("cost_center_number", data.cost_center_number); + frm.set_value("cost_center_name", data.cost_center_name); + frm.set_value("cost_center_number", data.cost_center_number); } d.hide(); } diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js index feecf93484..0898a09a1c 100644 --- a/erpnext/accounts/doctype/payment_term/payment_term.js +++ b/erpnext/accounts/doctype/payment_term/payment_term.js @@ -14,7 +14,7 @@ frappe.ui.form.on('Payment Term', { if (frm.doc.discount) { let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]); if (frm.doc.discount_type == 'Amount') { - description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]); + description = __("{0} will be given as discount.", [frm.doc.discount]); } frm.set_df_property("discount", "description", description); } diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js index b4cd852c32..e9d56784ff 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js @@ -1,8 +1,6 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* global frappe, refresh_field */ - frappe.ui.form.on("Supplier Scorecard", { setup: function(frm) { if (frm.doc.indicator_color !== "") { @@ -79,7 +77,7 @@ var loadAllStandings = function(frm) { callback: function(r) { for (var j = 0; j < frm.doc.standings.length; j++) { - if(!frm.doc.standings[j].hasOwnProperty("standing_name")) { + if(!Object.prototype.hasOwnProperty.call(frm.doc.standings[j], "standing_name")) { frm.get_field("standings").grid.grid_rows[j].remove(); } } diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js index dc5474e3b4..edf0b04d72 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js @@ -1,7 +1,6 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* global frappe, __ */ frappe.listview_settings["Supplier Scorecard"] = { add_fields: ["indicator_color", "status"], @@ -14,4 +13,4 @@ frappe.listview_settings["Supplier Scorecard"] = { } }, -}; +} diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js index 9f8a2dee81..2186cd89eb 100644 --- a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js @@ -1,8 +1,6 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -/* global frappe */ - frappe.ui.form.on("Supplier Scorecard Criteria", { refresh: function() {} }); diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index d03a07cf03..a2e4bdacac 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -208,7 +208,7 @@ erpnext.accounts.taxes = { erpnext.accounts.payment_triggers = { setup: function(doctype) { - frappe.form.ui.on(doctype, { + frappe.ui.form.on(doctype, { allocate_advances_automatically(frm) { frm.trigger('fetch_advances'); }, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 8633be8c42..09109f7a5e 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -637,7 +637,7 @@ erpnext.utils.update_child_items = function(opts) { }) } - new frappe.ui.Dialog({ + let dialog = new frappe.ui.Dialog({ title: __("Update Items"), size: "extra-large", fields: [ @@ -674,7 +674,9 @@ erpnext.utils.update_child_items = function(opts) { refresh_field("items"); }, primary_action_label: __('Update') - }).show(); + }) + + dialog.show(); } erpnext.utils.map_current_doc = function(opts) { diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 27a7968033..9267801839 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -382,7 +382,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { edit_full_form() { let bundle_id = this.item.serial_and_batch_bundle if (!bundle_id) { - _new = frappe.model.get_new_doc( + let _new = frappe.model.get_new_doc( "Serial and Batch Bundle", null, null, true ); diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index b09b715275..e02abb48e5 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -129,8 +129,6 @@ erpnext.stock.ItemDashboard = class ItemDashboard { context = this.get_item_dashboard_data(data, this.max_count, true); } - this.max_count = this.max_count; - // show more button if (data && data.length === (this.page_length + 1)) { this.content.find('.more').removeClass('hidden'); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index 6ff3ed3e8e..51a899b699 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -24,7 +24,7 @@ frappe.listview_settings['Delivery Note'] = { if (!doc.docstatus) { frappe.throw(__("Cannot create a Delivery Trip from Draft documents.")); } - }; + } frappe.new_doc("Delivery Trip") .then(() => { @@ -51,7 +51,7 @@ frappe.listview_settings['Delivery Note'] = { } }); }) - }; + } }; // doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false); @@ -66,4 +66,4 @@ frappe.listview_settings['Delivery Note'] = { erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip"); }); } -}; +} From 662ccd467c94e66c866d8646d29104846efb936a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 11:18:11 +0530 Subject: [PATCH 092/107] fix: allocation logic on 'Get Outstanding Invoices' btn in PE 1. fixed broken `payment_term` filter in Payment References section 2. Throw error if user fails to select 'Payment Term' for an invoice with 'Payment Term based allocation' enabled. --- .../doctype/payment_entry/payment_entry.js | 9 +++------ .../doctype/payment_entry/payment_entry.py | 18 ++++++++++++++---- erpnext/controllers/queries.py | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 0701435dfc..35092a7c4d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -122,13 +122,10 @@ frappe.ui.form.on('Payment Entry', { frm.set_query('payment_term', 'references', function(frm, cdt, cdn) { const child = locals[cdt][cdn]; if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) { - let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name}); - - payment_term_list = payment_term_list.map(pt => pt.payment_term); - return { + query: "erpnext.controllers.queries.get_payment_terms_for_references", filters: { - 'name': ['in', payment_term_list] + 'reference': child.reference_name } } } @@ -1463,4 +1460,4 @@ frappe.ui.form.on('Payment Entry', { }); } }, -}) \ No newline at end of file +}) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7542babe92..1e53c9bb48 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -228,10 +228,17 @@ class PaymentEntry(AccountsController): d = frappe._dict(d) latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d - for d in self.get("references"): - latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get( - d.payment_term - ) + for idx, d in enumerate(self.get("references"), start=1): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() + + if (d.payment_term is None or d.payment_term == "") and d.payment_term not in latest.keys(): + frappe.throw( + _( + "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" + ).format(frappe.bold(d.reference_name), frappe.bold(idx)) + ) + + latest = latest.get(d.payment_term) # The reference has already been fully paid if not latest: @@ -1633,6 +1640,9 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company): "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), "payment_term_outstanding": payment_term_outstanding, + "allocated_amount": payment_term_outstanding + if payment_term_outstanding + else d.outstanding_amount, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, "account": d.account, diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d1dcd6a109..5ec24743d9 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -874,3 +874,18 @@ def get_fields(doctype, fields=None): fields.insert(1, meta.title_field.strip()) return unique(fields) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, filters) -> list: + terms = [] + if filters: + terms = frappe.db.get_all( + "Payment Schedule", + filters={"parent": filters.get("reference")}, + fields=["payment_term"], + limit=page_len, + as_list=1, + ) + return terms From ec7558b9e043ec90c5492e2d8581a750b76e88af Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 23 Jul 2023 11:55:16 +0530 Subject: [PATCH 093/107] refactor: handle references without any template and `payment_term` --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1e53c9bb48..c175e2475d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -207,6 +207,16 @@ class PaymentEntry(AccountsController): if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): frappe.throw(fail_message.format(d.idx)) + def term_based_allocation_enabled_for_reference( + self, reference_doctype: str, reference_name: str + ) -> bool: + if reference_doctype and reference_name: + if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"): + return frappe.db.get_value( + "Payment Terms Template", template, "allocate_payment_based_on_payment_terms" + ) + return False + def validate_allocated_amount_with_latest_data(self): latest_references = get_outstanding_reference_documents( { @@ -231,14 +241,20 @@ class PaymentEntry(AccountsController): for idx, d in enumerate(self.get("references"), start=1): latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() - if (d.payment_term is None or d.payment_term == "") and d.payment_term not in latest.keys(): + # If term based allocation is enabled, throw + if ( + d.payment_term is None or d.payment_term == "" + ) and self.term_based_allocation_enabled_for_reference( + d.reference_doctype, d.reference_name + ): frappe.throw( _( "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" ).format(frappe.bold(d.reference_name), frappe.bold(idx)) ) - latest = latest.get(d.payment_term) + # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key + latest = latest.get(d.payment_term) or latest.get(None) # The reference has already been fully paid if not latest: From 7205fb9b97760360abdf9ca5a9d0d072d76e9c73 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Jul 2023 12:07:21 +0530 Subject: [PATCH 094/107] chore: convert sales common to utils --- .../doctype/pos_invoice/pos_invoice.js | 2 +- .../doctype/sales_invoice/sales_invoice.js | 7 +- .../crm/doctype/opportunity/opportunity.js | 17 +- erpnext/public/js/erpnext.bundle.js | 1 + erpnext/public/js/utils/sales_common.js | 436 ++++++++++++++++++ .../selling/doctype/quotation/quotation.js | 6 +- .../doctype/sales_order/sales_order.js | 4 +- erpnext/selling/sales_common.js | 431 ----------------- .../doctype/delivery_note/delivery_note.js | 3 +- 9 files changed, 455 insertions(+), 452 deletions(-) create mode 100644 erpnext/public/js/utils/sales_common.js delete mode 100644 erpnext/selling/sales_common.js diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 30c841e02b..6f0b8019b8 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -1,8 +1,8 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -{% include 'erpnext/selling/sales_common.js' %}; frappe.provide("erpnext.accounts"); +erpnext.sales_common.setup_selling_controller(); erpnext.accounts.pos.setup("POS Invoice"); erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 389c1644b2..a1066b90d0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -1,14 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -{% include 'erpnext/selling/sales_common.js' %}; frappe.provide("erpnext.accounts"); erpnext.accounts.taxes.setup_tax_validations("Sales Invoice"); erpnext.accounts.payment_triggers.setup("Sales Invoice"); erpnext.accounts.pos.setup("Sales Invoice"); erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); - +erpnext.sales_common.setup_selling_controller(); erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends erpnext.selling.SellingController { setup(doc) { this.setup_posting_date_time_check(); @@ -715,7 +714,7 @@ frappe.ui.form.on('Sales Invoice', { frm.set_query('pos_profile', function(doc) { if(!doc.company) { - frappe.throw(_('Please set Company')); + frappe.throw(__('Please set Company')); } return { @@ -862,7 +861,7 @@ frappe.ui.form.on('Sales Invoice', { kwargs = Object(); } - if (!kwargs.hasOwnProperty("project") && frm.doc.project) { + if (!Object.prototype.hasOwnProperty.call(kwargs, "project") && frm.doc.project) { kwargs.project = frm.doc.project; } diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index b2617955a3..149b32df12 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -1,10 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - -{% include 'erpnext/selling/sales_common.js' %} frappe.provide("erpnext.crm"); cur_frm.email_field = "contact_email"; + +erpnext.pre_sales.set_as_lost("Quotation"); +erpnext.sales_common.setup_selling_controller(); + + frappe.ui.form.on("Opportunity", { setup: function(frm) { frm.custom_make_buttons = { @@ -46,10 +49,6 @@ frappe.ui.form.on("Opportunity", { } }, - onload_post_render: function(frm) { - frm.get_field("items").grid.set_multiple_add("item_code", "qty"); - }, - status:function(frm){ if (frm.doc.status == "Lost"){ frm.trigger('set_as_lost_dialog'); @@ -252,13 +251,13 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller { onload() { if(!this.frm.doc.status) { - frm.set_value('status', 'Open'); + this.frm.set_value('status', 'Open'); } if(!this.frm.doc.company && frappe.defaults.get_user_default("Company")) { - frm.set_value('company', frappe.defaults.get_user_default("Company")); + this.frm.set_value('company', frappe.defaults.get_user_default("Company")); } if(!this.frm.doc.currency) { - frm.set_value('currency', frappe.defaults.get_user_default("Currency")); + this.frm.set_value('currency', frappe.defaults.get_user_default("Currency")); } this.setup_queries(); diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 3d5025dd25..71d7c171a0 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -26,5 +26,6 @@ import "./templates/crm_activities.html"; import "./templates/crm_notes.html"; import "./controllers/accounts.js" import "./utils/landed_taxes_and_charges_common.js"; +import "./utils/sales_common.js" // import { sum } from 'frappe/public/utils/util.js' diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js new file mode 100644 index 0000000000..430d9968e8 --- /dev/null +++ b/erpnext/public/js/utils/sales_common.js @@ -0,0 +1,436 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +// cur_frm.email_field = "contact_email"; + +frappe.provide("erpnext.selling"); + +erpnext.sales_common = { + setup_selling_controller:function() { + erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController { + setup() { + super.setup(); + } + + onload() { + super.onload(); + this.setup_queries(); + this.frm.set_query('shipping_rule', function() { + return { + filters: { + "shipping_rule_type": "Selling" + } + }; + }); + } + + setup_queries() { + var me = this; + + $.each([["customer", "customer"], + ["lead", "lead"]], + function(i, opts) { + if(me.frm.fields_dict[opts[0]]) + me.frm.set_query(opts[0], erpnext.queries[opts[1]]); + }); + + me.frm.set_query('contact_person', erpnext.queries.contact_query); + me.frm.set_query('customer_address', erpnext.queries.address_query); + me.frm.set_query('shipping_address_name', erpnext.queries.address_query); + me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query); + + erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype); + + if(this.frm.fields_dict.selling_price_list) { + this.frm.set_query("selling_price_list", function() { + return { filters: { selling: 1 } }; + }); + } + + if(this.frm.fields_dict.tc_name) { + this.frm.set_query("tc_name", function() { + return { filters: { selling: 1 } }; + }); + } + + if(!this.frm.fields_dict["items"]) { + return; + } + + if(this.frm.fields_dict["items"].grid.get_field('item_code')) { + this.frm.set_query("item_code", "items", function() { + return { + query: "erpnext.controllers.queries.item_query", + filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer, 'has_variants': 0} + } + }); + } + + if(this.frm.fields_dict["packed_items"] && + this.frm.fields_dict["packed_items"].grid.get_field('batch_no')) { + this.frm.set_query("batch_no", "packed_items", function(doc, cdt, cdn) { + return me.set_query_for_batch(doc, cdt, cdn) + }); + } + + if(this.frm.fields_dict["items"].grid.get_field('item_code')) { + this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) { + return me.set_query_for_item_tax_template(doc, cdt, cdn) + }); + } + + } + + refresh() { + super.refresh(); + + frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} + + this.frm.toggle_display("customer_name", + (this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer)); + + this.toggle_editable_price_list_rate(); + } + + customer() { + var me = this; + erpnext.utils.get_party_details(this.frm, null, null, function() { + me.apply_price_list(); + }); + } + + customer_address() { + erpnext.utils.get_address_display(this.frm, "customer_address"); + erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); + } + + shipping_address_name() { + erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address"); + erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name"); + } + + dispatch_address_name() { + erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address"); + } + + sales_partner() { + this.apply_pricing_rule(); + } + + campaign() { + this.apply_pricing_rule(); + } + + selling_price_list() { + this.apply_price_list(); + this.set_dynamic_labels(); + } + + discount_percentage(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + item.discount_amount = 0.0; + this.apply_discount_on_item(doc, cdt, cdn, 'discount_percentage'); + } + + discount_amount(doc, cdt, cdn) { + + if(doc.name === cdn) { + return; + } + + var item = frappe.get_doc(cdt, cdn); + item.discount_percentage = 0.0; + this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount'); + } + + apply_discount_on_item(doc, cdt, cdn, field) { + var item = frappe.get_doc(cdt, cdn); + if(!item.price_list_rate) { + item[field] = 0.0; + } else { + this.price_list_rate(doc, cdt, cdn); + } + this.set_gross_profit(item); + } + + commission_rate() { + this.calculate_commission(); + } + + total_commission() { + frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]); + + const { amount_eligible_for_commission } = this.frm.doc; + if(!amount_eligible_for_commission) return; + + this.frm.set_value( + "commission_rate", flt( + this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission + ) + ); + } + + allocated_percentage(doc, cdt, cdn) { + var sales_person = frappe.get_doc(cdt, cdn); + if(sales_person.allocated_percentage) { + + sales_person.allocated_percentage = flt(sales_person.allocated_percentage, + precision("allocated_percentage", sales_person)); + + sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission * + sales_person.allocated_percentage / 100.0, + precision("allocated_amount", sales_person)); + refresh_field(["allocated_amount"], sales_person); + + this.calculate_incentive(sales_person); + refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name, + sales_person.parentfield); + } + } + + sales_person(doc, cdt, cdn) { + var row = frappe.get_doc(cdt, cdn); + this.calculate_incentive(row); + refresh_field("incentives",row.name,row.parentfield); + } + + toggle_editable_price_list_rate() { + var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name); + var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate")); + + if(df && editable_price_list_rate) { + const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item"); + if (!this.frm.fields_dict[parent_field]) return; + + this.frm.fields_dict[parent_field].grid.update_docfield_property( + 'price_list_rate', 'read_only', 0 + ); + } + } + + calculate_commission() { + if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return; + + if(this.frm.doc.commission_rate > 100) { + this.frm.set_value("commission_rate", 100); + frappe.throw(`${__(frappe.meta.get_label( + this.frm.doc.doctype, "commission_rate", this.frm.doc.name + ))} ${__("cannot be greater than 100")}`); + } + + this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce( + (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0 + ) + + this.frm.doc.total_commission = flt( + this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0, + precision("total_commission") + ); + + refresh_field(["amount_eligible_for_commission", "total_commission"]); + } + + calculate_contribution() { + var me = this; + $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) { + frappe.model.round_floats_in(sales_person); + if (!sales_person.allocated_percentage) return; + + sales_person.allocated_amount = flt( + me.frm.doc.amount_eligible_for_commission + * sales_person.allocated_percentage + / 100.0, + precision("allocated_amount", sales_person) + ); + }); + } + + calculate_incentive(row) { + if(row.allocated_amount) + { + row.incentives = flt( + row.allocated_amount * row.commission_rate / 100.0, + precision("incentives", row)); + } + } + + set_dynamic_labels() { + super.set_dynamic_labels(); + this.set_product_bundle_help(this.frm.doc); + } + + set_product_bundle_help(doc) { + if(!cur_frm.fields_dict.packing_list) return; + if ((doc.packed_items || []).length) { + $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true); + + if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { + var help_msg = "
" + + __("For 'Product Bundle' items, Warehouse, Serial No and Batch No will be considered from the 'Packing List' table. If Warehouse and Batch No are same for all packing items for any 'Product Bundle' item, those values can be entered in the main Item table, values will be copied to 'Packing List' table.")+ + "
"; + frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = help_msg; + } + } else { + $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(false); + if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { + frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = ''; + } + } + refresh_field('product_bundle_help'); + } + + company_address() { + var me = this; + if(this.frm.doc.company_address) { + frappe.call({ + method: "frappe.contacts.doctype.address.address.get_address_display", + args: {"address_dict": this.frm.doc.company_address }, + callback: function(r) { + if(r.message) { + me.frm.set_value("company_address_display", r.message) + } + } + }) + } else { + this.frm.set_value("company_address_display", ""); + } + } + + conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) { + super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate); + } + + qty(doc, cdt, cdn) { + super.qty(doc, cdt, cdn); + } + + pick_serial_and_batch(doc, cdt, cdn) { + let item = locals[cdt][cdn]; + let me = this; + let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; + + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.type_of_transaction = item.qty > 0 ? "Outward":"Inward"; + + item.title = item.has_serial_no ? + __("Select Serial No") : __("Select Batch No"); + + if (item.has_serial_no && item.has_batch_no) { + item.title = __("Select Serial and Batch"); + } + + frappe.require(path, function() { + new erpnext.SerialBatchPackageSelector( + me.frm, item, (r) => { + if (r) { + frappe.model.set_value(item.doctype, item.name, { + "serial_and_batch_bundle": r.name, + "qty": Math.abs(r.total_qty) + }); + } + } + ); + }); + } + }); + } + + update_auto_repeat_reference(doc) { + if (doc.auto_repeat) { + frappe.call({ + method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference", + args:{ + docname: doc.auto_repeat, + reference:doc.name + }, + callback: function(r){ + if (r.message=="success") { + frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'}); + } else { + frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'}); + } + } + }) + } + } + + project() { + let me = this; + if(in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) { + if(this.frm.doc.project) { + frappe.call({ + method:'erpnext.projects.doctype.project.project.get_cost_center_name' , + args: {project: this.frm.doc.project}, + callback: function(r, rt) { + if(!r.exc) { + $.each(me.frm.doc["items"] || [], function(i, row) { + if(r.message) { + frappe.model.set_value(row.doctype, row.name, "cost_center", r.message); + frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message])); + } + }) + } + } + }) + } + } + } + }; + } +} + +erpnext.pre_sales = { + set_as_lost: function(doctype) { + frappe.ui.form.on(doctype, { + set_as_lost_dialog: function(frm) { + var dialog = new frappe.ui.Dialog({ + title: __("Set as Lost"), + fields: [ + { + "fieldtype": "Table MultiSelect", + "label": __("Lost Reasons"), + "fieldname": "lost_reason", + "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail', + "reqd": 1 + }, + { + "fieldtype": "Table MultiSelect", + "label": __("Competitors"), + "fieldname": "competitors", + "options": "Competitor Detail" + }, + { + "fieldtype": "Small Text", + "label": __("Detailed Reason"), + "fieldname": "detailed_reason" + }, + ], + primary_action: function() { + let values = dialog.get_values(); + + frm.call({ + doc: frm.doc, + method: 'declare_enquiry_lost', + args: { + 'lost_reasons_list': values.lost_reason, + 'competitors': values.competitors ? values.competitors : [], + 'detailed_reason': values.detailed_reason + }, + callback: function(r) { + dialog.hide(); + frm.reload_doc(); + }, + }); + }, + primary_action_label: __('Declare Lost') + }); + + dialog.show(); + } + }); + } +} \ No newline at end of file diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 67c392cc3f..ea55cb24e7 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -1,8 +1,10 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - -{% include 'erpnext/selling/sales_common.js' %} +erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template"); +erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); +erpnext.pre_sales.set_as_lost("Quotation"); +erpnext.sales_common.setup_selling_controller(); frappe.ui.form.on('Quotation', { setup: function(frm) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 2707ded5e3..b57a09464e 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -1,10 +1,9 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -{% include 'erpnext/selling/sales_common.js' %} - erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Sales Order"); +erpnext.sales_common.setup_selling_controller(); frappe.ui.form.on("Sales Order", { setup: function(frm) { @@ -817,7 +816,6 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex var method = args.against_default_supplier ? "make_purchase_order_for_default_supplier" : "make_purchase_order" return frappe.call({ method: "erpnext.selling.doctype.sales_order.sales_order." + method, - freeze: true, freeze_message: __("Creating Purchase Order ..."), args: { "source_name": me.frm.doc.name, diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js deleted file mode 100644 index 87c0fae42a..0000000000 --- a/erpnext/selling/sales_common.js +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - - -cur_frm.cscript.tax_table = "Sales Taxes and Charges"; -{% include 'erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js' %} - - -cur_frm.email_field = "contact_email"; - -frappe.provide("erpnext.selling"); -erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController { - setup() { - super.setup(); - } - - onload() { - super.onload(); - this.setup_queries(); - this.frm.set_query('shipping_rule', function() { - return { - filters: { - "shipping_rule_type": "Selling" - } - }; - }); - } - - setup_queries() { - var me = this; - - $.each([["customer", "customer"], - ["lead", "lead"]], - function(i, opts) { - if(me.frm.fields_dict[opts[0]]) - me.frm.set_query(opts[0], erpnext.queries[opts[1]]); - }); - - me.frm.set_query('contact_person', erpnext.queries.contact_query); - me.frm.set_query('customer_address', erpnext.queries.address_query); - me.frm.set_query('shipping_address_name', erpnext.queries.address_query); - me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query); - - erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype); - - if(this.frm.fields_dict.selling_price_list) { - this.frm.set_query("selling_price_list", function() { - return { filters: { selling: 1 } }; - }); - } - - if(this.frm.fields_dict.tc_name) { - this.frm.set_query("tc_name", function() { - return { filters: { selling: 1 } }; - }); - } - - if(!this.frm.fields_dict["items"]) { - return; - } - - if(this.frm.fields_dict["items"].grid.get_field('item_code')) { - this.frm.set_query("item_code", "items", function() { - return { - query: "erpnext.controllers.queries.item_query", - filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer, 'has_variants': 0} - } - }); - } - - if(this.frm.fields_dict["packed_items"] && - this.frm.fields_dict["packed_items"].grid.get_field('batch_no')) { - this.frm.set_query("batch_no", "packed_items", function(doc, cdt, cdn) { - return me.set_query_for_batch(doc, cdt, cdn) - }); - } - - if(this.frm.fields_dict["items"].grid.get_field('item_code')) { - this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) { - return me.set_query_for_item_tax_template(doc, cdt, cdn) - }); - } - - } - - refresh() { - super.refresh(); - - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} - - this.frm.toggle_display("customer_name", - (this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer)); - - this.toggle_editable_price_list_rate(); - } - - customer() { - var me = this; - erpnext.utils.get_party_details(this.frm, null, null, function() { - me.apply_price_list(); - }); - } - - customer_address() { - erpnext.utils.get_address_display(this.frm, "customer_address"); - erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); - } - - shipping_address_name() { - erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address"); - erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name"); - } - - dispatch_address_name() { - erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address"); - } - - sales_partner() { - this.apply_pricing_rule(); - } - - campaign() { - this.apply_pricing_rule(); - } - - selling_price_list() { - this.apply_price_list(); - this.set_dynamic_labels(); - } - - discount_percentage(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - item.discount_amount = 0.0; - this.apply_discount_on_item(doc, cdt, cdn, 'discount_percentage'); - } - - discount_amount(doc, cdt, cdn) { - - if(doc.name === cdn) { - return; - } - - var item = frappe.get_doc(cdt, cdn); - item.discount_percentage = 0.0; - this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount'); - } - - apply_discount_on_item(doc, cdt, cdn, field) { - var item = frappe.get_doc(cdt, cdn); - if(!item.price_list_rate) { - item[field] = 0.0; - } else { - this.price_list_rate(doc, cdt, cdn); - } - this.set_gross_profit(item); - } - - commission_rate() { - this.calculate_commission(); - } - - total_commission() { - frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]); - - const { amount_eligible_for_commission } = this.frm.doc; - if(!amount_eligible_for_commission) return; - - this.frm.set_value( - "commission_rate", flt( - this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission - ) - ); - } - - allocated_percentage(doc, cdt, cdn) { - var sales_person = frappe.get_doc(cdt, cdn); - if(sales_person.allocated_percentage) { - - sales_person.allocated_percentage = flt(sales_person.allocated_percentage, - precision("allocated_percentage", sales_person)); - - sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission * - sales_person.allocated_percentage / 100.0, - precision("allocated_amount", sales_person)); - refresh_field(["allocated_amount"], sales_person); - - this.calculate_incentive(sales_person); - refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name, - sales_person.parentfield); - } - } - - sales_person(doc, cdt, cdn) { - var row = frappe.get_doc(cdt, cdn); - this.calculate_incentive(row); - refresh_field("incentives",row.name,row.parentfield); - } - - toggle_editable_price_list_rate() { - var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name); - var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate")); - - if(df && editable_price_list_rate) { - const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item"); - if (!this.frm.fields_dict[parent_field]) return; - - this.frm.fields_dict[parent_field].grid.update_docfield_property( - 'price_list_rate', 'read_only', 0 - ); - } - } - - calculate_commission() { - if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return; - - if(this.frm.doc.commission_rate > 100) { - this.frm.set_value("commission_rate", 100); - frappe.throw(`${__(frappe.meta.get_label( - this.frm.doc.doctype, "commission_rate", this.frm.doc.name - ))} ${__("cannot be greater than 100")}`); - } - - this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce( - (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0 - ) - - this.frm.doc.total_commission = flt( - this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0, - precision("total_commission") - ); - - refresh_field(["amount_eligible_for_commission", "total_commission"]); - } - - calculate_contribution() { - var me = this; - $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) { - frappe.model.round_floats_in(sales_person); - if (!sales_person.allocated_percentage) return; - - sales_person.allocated_amount = flt( - me.frm.doc.amount_eligible_for_commission - * sales_person.allocated_percentage - / 100.0, - precision("allocated_amount", sales_person) - ); - }); - } - - calculate_incentive(row) { - if(row.allocated_amount) - { - row.incentives = flt( - row.allocated_amount * row.commission_rate / 100.0, - precision("incentives", row)); - } - } - - set_dynamic_labels() { - super.set_dynamic_labels(); - this.set_product_bundle_help(this.frm.doc); - } - - set_product_bundle_help(doc) { - if(!cur_frm.fields_dict.packing_list) return; - if ((doc.packed_items || []).length) { - $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true); - - if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { - var help_msg = "
" + - __("For 'Product Bundle' items, Warehouse, Serial No and Batch No will be considered from the 'Packing List' table. If Warehouse and Batch No are same for all packing items for any 'Product Bundle' item, those values can be entered in the main Item table, values will be copied to 'Packing List' table.")+ - "
"; - frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = help_msg; - } - } else { - $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(false); - if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { - frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = ''; - } - } - refresh_field('product_bundle_help'); - } - - company_address() { - var me = this; - if(this.frm.doc.company_address) { - frappe.call({ - method: "frappe.contacts.doctype.address.address.get_address_display", - args: {"address_dict": this.frm.doc.company_address }, - callback: function(r) { - if(r.message) { - me.frm.set_value("company_address_display", r.message) - } - } - }) - } else { - this.frm.set_value("company_address_display", ""); - } - } - - conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) { - super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate); - } - - qty(doc, cdt, cdn) { - super.qty(doc, cdt, cdn); - } - - pick_serial_and_batch(doc, cdt, cdn) { - let item = locals[cdt][cdn]; - let me = this; - let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; - - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) - .then((r) => { - if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { - item.has_serial_no = r.message.has_serial_no; - item.has_batch_no = r.message.has_batch_no; - item.type_of_transaction = item.qty > 0 ? "Outward":"Inward"; - - item.title = item.has_serial_no ? - __("Select Serial No") : __("Select Batch No"); - - if (item.has_serial_no && item.has_batch_no) { - item.title = __("Select Serial and Batch"); - } - - frappe.require(path, function() { - new erpnext.SerialBatchPackageSelector( - me.frm, item, (r) => { - if (r) { - frappe.model.set_value(item.doctype, item.name, { - "serial_and_batch_bundle": r.name, - "qty": Math.abs(r.total_qty) - }); - } - } - ); - }); - } - }); - } - - update_auto_repeat_reference(doc) { - if (doc.auto_repeat) { - frappe.call({ - method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference", - args:{ - docname: doc.auto_repeat, - reference:doc.name - }, - callback: function(r){ - if (r.message=="success") { - frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'}); - } else { - frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'}); - } - } - }) - } - } -}; - -frappe.ui.form.on(cur_frm.doctype,"project", function(frm) { - if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) { - if(frm.doc.project) { - frappe.call({ - method:'erpnext.projects.doctype.project.project.get_cost_center_name' , - args: { project: frm.doc.project }, - callback: function(r, rt) { - if(!r.exc) { - $.each(frm.doc["items"] || [], function(i, row) { - if(r.message) { - frappe.model.set_value(row.doctype, row.name, "cost_center", r.message); - frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message])); - } - }) - } - } - }) - } - } -}) - -frappe.ui.form.on(cur_frm.doctype, { - set_as_lost_dialog: function(frm) { - var dialog = new frappe.ui.Dialog({ - title: __("Set as Lost"), - fields: [ - { - "fieldtype": "Table MultiSelect", - "label": __("Lost Reasons"), - "fieldname": "lost_reason", - "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail', - "reqd": 1 - }, - { - "fieldtype": "Table MultiSelect", - "label": __("Competitors"), - "fieldname": "competitors", - "options": "Competitor Detail" - }, - { - "fieldtype": "Small Text", - "label": __("Detailed Reason"), - "fieldname": "detailed_reason" - }, - ], - primary_action: function() { - let values = dialog.get_values(); - - frm.call({ - doc: frm.doc, - method: 'declare_enquiry_lost', - args: { - 'lost_reasons_list': values.lost_reason, - 'competitors': values.competitors ? values.competitors : [], - 'detailed_reason': values.detailed_reason - }, - callback: function(r) { - dialog.hide(); - frm.reload_doc(); - }, - }); - }, - primary_action_label: __('Declare Lost') - }); - - dialog.show(); - } -}) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 1df797301e..eee7972629 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -1,8 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -{% include 'erpnext/selling/sales_common.js' %}; - cur_frm.add_fetch('customer', 'tax_id', 'tax_id'); frappe.provide("erpnext.stock"); @@ -11,6 +9,7 @@ frappe.provide("erpnext.accounts.dimensions"); erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Delivery Note"); +erpnext.sales_common.setup_selling_controller(); frappe.ui.form.on("Delivery Note", { From 8ccb8e3c5b69315aa6450783e82ef97a5ebf9681 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Jul 2023 18:50:44 +0530 Subject: [PATCH 095/107] chore: Move buying controller to utils --- .../purchase_invoice/purchase_invoice.js | 6 +- .../doctype/sales_invoice/regional/italy.js | 26 +- .../doctype/purchase_order/purchase_order.js | 3 +- .../request_for_quotation.js | 12 +- .../supplier_quotation/supplier_quotation.js | 4 +- erpnext/public/js/controllers/buying.js | 756 +++++++++--------- erpnext/public/js/erpnext.bundle.js | 3 +- erpnext/regional/italy/sales_invoice.js | 25 - .../material_request/material_request.js | 8 +- .../purchase_receipt/purchase_receipt.js | 3 +- 10 files changed, 418 insertions(+), 428 deletions(-) delete mode 100644 erpnext/regional/italy/sales_invoice.js diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 0f581e20da..c19413d811 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -2,11 +2,11 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.accounts"); -{% include 'erpnext/public/js/controllers/buying.js' %}; erpnext.accounts.payment_triggers.setup("Purchase Invoice"); erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice"); +erpnext.buying.setup_buying_controller(); erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController { setup(doc) { @@ -550,8 +550,8 @@ frappe.ui.form.on("Purchase Invoice", { }, mode_of_payment: function(frm) { - get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){ - frm.set_value('cash_bank_account', account); + erpnext.accounts.pos.get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account) { + frm.set_value("cash_bank_account", account); }) }, diff --git a/erpnext/accounts/doctype/sales_invoice/regional/italy.js b/erpnext/accounts/doctype/sales_invoice/regional/italy.js index 21eb8ce661..0c20f364a4 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/italy.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/italy.js @@ -1,3 +1,23 @@ -{% include "erpnext/regional/italy/sales_invoice.js" %} - -erpnext.setup_e_invoice_button('Sales Invoice') +frappe.ui.form.on("Sales Invoice", { + refresh: (frm) => { + if(frm.doc.docstatus == 1) { + frm.add_custom_button('Generate E-Invoice', () => { + frm.call({ + method: "erpnext.regional.italy.utils.generate_single_invoice", + args: { + docname: frm.doc.name + }, + callback: function(r) { + frm.reload_doc(); + if(r.message) { + open_url_post(frappe.request.url, { + cmd: 'frappe.core.doctype.file.file.download_file', + file_url: r.message + }); + } + } + }); + }); + } + } +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 97b7cf874c..db3a60e942 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -3,11 +3,10 @@ frappe.provide("erpnext.buying"); frappe.provide("erpnext.accounts.dimensions"); -{% include 'erpnext/public/js/controllers/buying.js' %}; erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Purchase Order"); - +erpnext.buying.setup_buying_controller(); frappe.ui.form.on("Purchase Order", { setup: function(frm) { diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 2f0b7862a8..0cdb915cdc 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -1,11 +1,10 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - -{% include 'erpnext/public/js/controllers/buying.js' %}; - cur_frm.add_fetch('contact', 'email_id', 'email_id') +erpnext.buying.setup_buying_controller(); + frappe.ui.form.on("Request for Quotation",{ setup: function(frm) { frm.custom_make_buttons = { @@ -436,7 +435,7 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll //Remove blanks for (var j = 0; j < frm.doc.suppliers.length; j++) { - if(!frm.doc.suppliers[j].hasOwnProperty("supplier")) { + if(!Object.prototype.hasOwnProperty.call(frm.doc.suppliers[j], "supplier")) { frm.get_field("suppliers").grid.grid_rows[j].remove(); } } @@ -445,10 +444,11 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll if(r.message) { for (var i = 0; i < r.message.length; i++) { var exists = false; + let supplier = ""; if (r.message[i].constructor === Array){ - var supplier = r.message[i][0]; + supplier = r.message[i][0]; } else { - var supplier = r.message[i].name; + supplier = r.message[i].name; } for (var j = 0; j < doc.suppliers.length;j++) { diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index dc9c590dc5..addf5a5e77 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -1,9 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -// attach required files -{% include 'erpnext/public/js/controllers/buying.js' %}; - +erpnext.buying.setup_buying_controller(); erpnext.buying.SupplierQuotationController = class SupplierQuotationController extends erpnext.buying.BuyingController { setup() { this.frm.custom_make_buttons = { diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 75d9891b81..7dbe485d29 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -3,421 +3,419 @@ frappe.provide("erpnext.buying"); -cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; +// cur_frm.email_field = "contact_email"; +// cur_frm.add_fetch('project', 'cost_center', 'cost_center'); -{% include 'erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js' %} +erpnext.buying = { + setup_buying_controller: function() { + erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController { + setup() { + super.setup(); + } -cur_frm.email_field = "contact_email"; + onload(doc, cdt, cdn) { + this.setup_queries(doc, cdt, cdn); + super.onload(); -erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController { - setup() { - super.setup(); - } - - onload(doc, cdt, cdn) { - this.setup_queries(doc, cdt, cdn); - super.onload(); - - this.frm.set_query('shipping_rule', function() { - return { - filters: { - "shipping_rule_type": "Buying" - } - }; - }); - - if (this.frm.doc.__islocal - && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) { - - var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total"); - var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total); - this.frm.set_value("disable_rounded_total", disable); - } - - - // no idea where me is coming from - if(this.frm.get_field('shipping_address')) { - this.frm.set_query("shipping_address", function() { - if(me.frm.doc.customer) { + this.frm.set_query('shipping_rule', function() { return { - query: 'frappe.contacts.doctype.address.address.address_query', - filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } + filters: { + "shipping_rule_type": "Buying" + } }; - } else - return erpnext.queries.company_address_query(me.frm.doc) - }); - } - /* eslint-enable */ - } + }); - setup_queries(doc, cdt, cdn) { - var me = this; + if (this.frm.doc.__islocal + && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) { - if(this.frm.fields_dict.buying_price_list) { - this.frm.set_query("buying_price_list", function() { - return{ - filters: { 'buying': 1 } - } - }); - } - - if(this.frm.fields_dict.tc_name) { - this.frm.set_query("tc_name", function() { - return{ - filters: { 'buying': 1 } - } - }); - } - - me.frm.set_query('supplier', erpnext.queries.supplier); - me.frm.set_query('contact_person', erpnext.queries.contact_query); - me.frm.set_query('supplier_address', erpnext.queries.address_query); - - me.frm.set_query('billing_address', erpnext.queries.company_address_query); - erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype); - - if(this.frm.fields_dict.supplier) { - this.frm.set_query("supplier", function() { - return{ query: "erpnext.controllers.queries.supplier_query" }}); - } - - this.frm.set_query("item_code", "items", function() { - if (me.frm.doc.is_subcontracted) { - var filters = {'supplier': me.frm.doc.supplier}; - if (me.frm.doc.is_old_subcontracting_flow) { - filters["is_sub_contracted_item"] = 1; - } - else { - filters["is_stock_item"] = 0; + var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total"); + var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total); + this.frm.set_value("disable_rounded_total", disable); } - return{ - query: "erpnext.controllers.queries.item_query", - filters: filters + + // no idea where me is coming from + if(this.frm.get_field('shipping_address')) { + this.frm.set_query("shipping_address", function() { + if(me.frm.doc.customer) { + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } + }; + } else + return erpnext.queries.company_address_query(me.frm.doc) + }); } } - else { - return{ - query: "erpnext.controllers.queries.item_query", - filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1, 'has_variants': 0} + + setup_queries(doc, cdt, cdn) { + var me = this; + + if(this.frm.fields_dict.buying_price_list) { + this.frm.set_query("buying_price_list", function() { + return{ + filters: { 'buying': 1 } + } + }); } - } - }); - - this.frm.set_query("manufacturer", "items", function(doc, cdt, cdn) { - const row = locals[cdt][cdn]; - return { - query: "erpnext.controllers.queries.item_manufacturer_query", - filters:{ 'item_code': row.item_code } - } - }); - - if(this.frm.fields_dict["items"].grid.get_field('item_code')) { - this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) { - return me.set_query_for_item_tax_template(doc, cdt, cdn) - }); - } - } - - refresh(doc) { - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier'}; - - this.frm.toggle_display("supplier_name", - (this.frm.doc.supplier_name && this.frm.doc.supplier_name!==this.frm.doc.supplier)); - - if(this.frm.doc.docstatus==0 && - (this.frm.doctype==="Purchase Order" || this.frm.doctype==="Material Request")) { - this.set_from_product_bundle(); - } - - this.toggle_subcontracting_fields(); - super.refresh(); - } - - toggle_subcontracting_fields() { - if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) { - this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', - 'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM'); - - this.frm.set_df_property('supplied_items', 'cannot_add_rows', 1); - this.frm.set_df_property('supplied_items', 'cannot_delete_rows', 1); - } - } - - supplier() { - var me = this; - erpnext.utils.get_party_details(this.frm, null, null, function(){ - me.apply_price_list(); - }); - } - - supplier_address() { - erpnext.utils.get_address_display(this.frm); - erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); - } - - buying_price_list() { - this.apply_price_list(); - } - - discount_percentage(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - item.discount_amount = 0.0; - this.price_list_rate(doc, cdt, cdn); - } - - discount_amount(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - item.discount_percentage = 0.0; - this.price_list_rate(doc, cdt, cdn); - } - - qty(doc, cdt, cdn) { - if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) { - this.calculate_received_qty(doc, cdt, cdn) - } - super.qty(doc, cdt, cdn); - } - - rejected_qty(doc, cdt, cdn) { - this.calculate_received_qty(doc, cdt, cdn) - } - - calculate_received_qty(doc, cdt, cdn){ - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["qty", "rejected_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "rejected_qty"])){ return } - - let received_qty = flt(item.qty + item.rejected_qty, precision("received_qty", item)); - let received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(received_qty); - - frappe.model.set_value(cdt, cdn, "received_qty", received_qty); - frappe.model.set_value(cdt, cdn, "received_stock_qty", received_stock_qty); - } - - batch_no(doc, cdt, cdn) { - super.batch_no(doc, cdt, cdn); - } - - validate_negative_quantity(cdt, cdn, item, fieldnames){ - if(!item || !fieldnames) { return } - - var is_negative_qty = false; - for(var i = 0; i{0} is invalid", [row.manufacturer_part_no]), - title: __("Invalid Part Number") + warehouse(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + if(item.item_code && item.warehouse) { + return this.frm.call({ + method: "erpnext.stock.get_item_details.get_bin_details", + child: item, + args: { + item_code: item.item_code, + warehouse: item.warehouse, + company: doc.company, + include_child_warehouses: true } - frappe.throw(msg); - } - } - ); - } - } - - add_serial_batch_bundle(doc, cdt, cdn) { - let item = locals[cdt][cdn]; - let me = this; - let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; - - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) - .then((r) => { - if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { - item.has_serial_no = r.message.has_serial_no; - item.has_batch_no = r.message.has_batch_no; - item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; - item.is_rejected = false; - - frappe.require(path, function() { - new erpnext.SerialBatchPackageSelector( - me.frm, item, (r) => { - if (r) { - let update_values = { - "serial_and_batch_bundle": r.name, - "qty": Math.abs(r.total_qty) - } - - if (r.warehouse) { - update_values["warehouse"] = r.warehouse; - } - - frappe.model.set_value(item.doctype, item.name, update_values); - } - } - ); }); } - }); - } + } - add_serial_batch_for_rejected_qty(doc, cdt, cdn) { - let item = locals[cdt][cdn]; - let me = this; - let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; - - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) - .then((r) => { - if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { - item.has_serial_no = r.message.has_serial_no; - item.has_batch_no = r.message.has_batch_no; - item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; - item.is_rejected = true; - - frappe.require(path, function() { - new erpnext.SerialBatchPackageSelector( - me.frm, item, (r) => { - if (r) { - let update_values = { - "serial_and_batch_bundle": r.name, - "rejected_qty": Math.abs(r.total_qty) - } - - if (r.warehouse) { - update_values["rejected_warehouse"] = r.warehouse; - } - - frappe.model.set_value(item.doctype, item.name, update_values); - } + project(doc, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + if(item.project) { + $.each(this.frm.doc["items"] || [], + function(i, other_item) { + if(!other_item.project) { + other_item.project = item.project; + refresh_field("project", other_item.name, other_item.parentfield); } - ); + }); + } + } + + rejected_warehouse(doc, cdt) { + // trigger autofill_warehouse only if parent rejected_warehouse field is triggered + if (["Purchase Invoice", "Purchase Receipt"].includes(cdt)) { + this.autofill_warehouse(doc.items, "rejected_warehouse", doc.rejected_warehouse); + } + } + + category(doc, cdt, cdn) { + // should be the category field of tax table + if(cdt != doc.doctype) { + this.calculate_taxes_and_totals(); + } + } + add_deduct_tax(doc, cdt, cdn) { + this.calculate_taxes_and_totals(); + } + + set_from_product_bundle() { + var me = this; + this.frm.add_custom_button(__("Product Bundle"), function() { + erpnext.buying.get_items_from_product_bundle(me.frm); + }, __("Get Items From")); + } + + shipping_address(){ + var me = this; + erpnext.utils.get_address_display(this.frm, "shipping_address", + "shipping_address_display", true); + } + + billing_address() { + erpnext.utils.get_address_display(this.frm, "billing_address", + "billing_address_display", true); + } + + tc_name() { + this.get_terms(); + } + + update_auto_repeat_reference(doc) { + if (doc.auto_repeat) { + frappe.call({ + method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference", + args:{ + docname: doc.auto_repeat, + reference:doc.name + }, + callback: function(r){ + if (r.message=="success") { + frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'}); + } else { + frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'}); + } + } + }) + } + } + + manufacturer(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + + if(row.manufacturer) { + frappe.call({ + method: "erpnext.stock.doctype.item_manufacturer.item_manufacturer.get_item_manufacturer_part_no", + args: { + 'item_code': row.item_code, + 'manufacturer': row.manufacturer + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, 'manufacturer_part_no', r.message); + } + } }); } - }); - } -}; + } -cur_frm.add_fetch('project', 'cost_center', 'cost_center'); + manufacturer_part_no(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + + if (row.manufacturer_part_no) { + frappe.model.get_value('Item Manufacturer', + { + 'item_code': row.item_code, + 'manufacturer': row.manufacturer, + 'manufacturer_part_no': row.manufacturer_part_no + }, + 'name', + function(data) { + if (!data) { + let msg = { + message: __("Manufacturer Part Number {0} is invalid", [row.manufacturer_part_no]), + title: __("Invalid Part Number") + } + frappe.throw(msg); + } + } + ); + } + } + + add_serial_batch_bundle(doc, cdt, cdn) { + let item = locals[cdt][cdn]; + let me = this; + let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; + + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; + item.is_rejected = false; + + frappe.require(path, function() { + new erpnext.SerialBatchPackageSelector( + me.frm, item, (r) => { + if (r) { + let update_values = { + "serial_and_batch_bundle": r.name, + "qty": Math.abs(r.total_qty) + } + + if (r.warehouse) { + update_values["warehouse"] = r.warehouse; + } + + frappe.model.set_value(item.doctype, item.name, update_values); + } + } + ); + }); + } + }); + } + + add_serial_batch_for_rejected_qty(doc, cdt, cdn) { + let item = locals[cdt][cdn]; + let me = this; + let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; + + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; + item.is_rejected = true; + + frappe.require(path, function() { + new erpnext.SerialBatchPackageSelector( + me.frm, item, (r) => { + if (r) { + let update_values = { + "serial_and_batch_bundle": r.name, + "rejected_qty": Math.abs(r.total_qty) + } + + if (r.warehouse) { + update_values["rejected_warehouse"] = r.warehouse; + } + + frappe.model.set_value(item.doctype, item.name, update_values); + } + } + ); + }); + } + }); + } + }; + } +} erpnext.buying.link_to_mrs = function(frm) { frappe.call({ diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 71d7c171a0..d7bea7b834 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -26,6 +26,7 @@ import "./templates/crm_activities.html"; import "./templates/crm_notes.html"; import "./controllers/accounts.js" import "./utils/landed_taxes_and_charges_common.js"; -import "./utils/sales_common.js" +import "./utils/sales_common.js"; +import "./controllers/buying.js"; // import { sum } from 'frappe/public/utils/util.js' diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js deleted file mode 100644 index b54ac53812..0000000000 --- a/erpnext/regional/italy/sales_invoice.js +++ /dev/null @@ -1,25 +0,0 @@ -erpnext.setup_e_invoice_button = (doctype) => { - frappe.ui.form.on(doctype, { - refresh: (frm) => { - if(frm.doc.docstatus == 1) { - frm.add_custom_button('Generate E-Invoice', () => { - frm.call({ - method: "erpnext.regional.italy.utils.generate_single_invoice", - args: { - docname: frm.doc.name - }, - callback: function(r) { - frm.reload_doc(); - if(r.message) { - open_url_post(frappe.request.url, { - cmd: 'frappe.core.doctype.file.file.download_file', - file_url: r.message - }); - } - } - }); - }); - } - } - }); -}; diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index c1f1b0d135..989bfd0d1d 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -3,7 +3,7 @@ // eslint-disable-next-line frappe.provide("erpnext.accounts.dimensions"); -{% include 'erpnext/public/js/controllers/buying.js' %}; +erpnext.buying.setup_buying_controller(); frappe.ui.form.on('Material Request', { setup: function(frm) { @@ -472,13 +472,13 @@ erpnext.buying.MaterialRequestController = class MaterialRequestController exten set_schedule_date(this.frm); } - onload(doc, cdt, cdn) { - this.frm.set_query("item_code", "items", function() { + onload() { + this.frm.set_query("item_code", "items", function(doc, cdt, cdn) { if (doc.material_request_type == "Customer Provided") { return{ query: "erpnext.controllers.queries.item_query", filters:{ - 'customer': me.frm.doc.customer, + 'customer': doc.customer, 'is_stock_item':1 } } diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 5eb7c55087..136553ae28 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -1,12 +1,11 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -{% include 'erpnext/public/js/controllers/buying.js' %}; - frappe.provide("erpnext.stock"); erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges"); erpnext.accounts.taxes.setup_tax_validations("Purchase Receipt"); +erpnext.buying.setup_buying_controller(); frappe.ui.form.on("Purchase Receipt", { setup: (frm) => { From 0d70ae2a21b19a216f4bc24dbb5ce40fc0c5ba76 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Jul 2023 19:34:08 +0530 Subject: [PATCH 096/107] chore: Ingnore issues --- .pre-commit-config.yaml | 3 +- erpnext/accounts/doctype/bank/bank.js | 2 +- .../doctype/plaid_settings/plaid_settings.js | 2 +- .../doctype/job_card/job_card.js | 44 +++++++++---------- .../page/point_of_sale/pos_controller.js | 2 +- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b01b56a856..2c9a60c7c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,8 @@ repos: .*node_modules.*| .*boilerplate.*| erpnext/public/js/controllers/.*| - erpnext/templates/pages/order.js + erpnext/templates/pages/order.js| + erpnext/templates/includes/.* )$ - repo: https://github.com/PyCQA/flake8 diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 6667193a54..83bd7fe862 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -102,7 +102,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { } onScriptLoaded(me) { - me.linkHandler = Plaid.create({ + me.linkHandler = Plaid.create({ // eslint-disable-line no-undef env: me.plaid_env, token: me.token, onSuccess: me.plaid_success diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index 3ba6bb9987..015e943b80 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -96,7 +96,7 @@ erpnext.integrations.plaidLink = class plaidLink { } onScriptLoaded(me) { - me.linkHandler = Plaid.create({ + me.linkHandler = Plaid.create({ // eslint-disable-line no-undef clientName: me.client_name, product: me.product, env: me.plaid_env, diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 8e9f542362..f1e6094813 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -344,6 +344,28 @@ frappe.ui.form.on('Job Card', { if(frm.doc.__islocal) return; + function setCurrentIncrement() { + currentIncrement += 1; + return currentIncrement; + } + + function updateStopwatch(increment) { + var hours = Math.floor(increment / 3600); + var minutes = Math.floor((increment - (hours * 3600)) / 60); + var seconds = increment - (hours * 3600) - (minutes * 60); + + $(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString()); + $(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString()); + $(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString()); + } + + function initialiseTimer() { + const interval = setInterval(function() { + var current = setCurrentIncrement(); + updateStopwatch(current); + }, 1000); + } + frm.dashboard.refresh(); const timer = `
Date: Sun, 23 Jul 2023 19:50:14 +0530 Subject: [PATCH 097/107] refactor: refresh table once after loop ends --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 4ec103c9f2..3a0a2cf8ce 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -897,6 +897,8 @@ frappe.ui.form.on('Sales Invoice', { frm.events.append_time_log(frm, timesheet, 1.0); } }); + frm.refresh_field("timesheets"); + frm.trigger("calculate_timesheet_totals"); }, async get_exchange_rate(frm, from_currency, to_currency) { @@ -936,9 +938,6 @@ frappe.ui.form.on('Sales Invoice', { row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate); row.timesheet_detail = time_log.name; row.project_name = time_log.project_name; - - frm.refresh_field("timesheets"); - frm.trigger("calculate_timesheet_totals"); }, calculate_timesheet_totals: function(frm) { From 8d3d9493f05ce4e1db22cb20c2dcb6d262d7a110 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Jul 2023 20:06:41 +0530 Subject: [PATCH 098/107] chore: linting issues --- .../stock/doctype/item_variant_settings/item_variant_settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py index cec5e218cc..c3edba321c 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py @@ -24,7 +24,6 @@ class ItemVariantSettings(Document): "description", "variant_of", "valuation_rate", - "description", "barcodes", "has_variants", "attributes", From cdc9b62688c803dc208a8e6ef7c70dcfca46cc08 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Jul 2023 22:44:18 +0530 Subject: [PATCH 099/107] chore: Remove cur_frm from sales_invoice.js --- .../accounts/doctype/sales_invoice/regional/italy.js | 2 +- erpnext/crm/doctype/opportunity/opportunity.js | 5 ++--- erpnext/public/js/controllers/buying.js | 3 +-- erpnext/public/js/utils/sales_common.js | 11 +++++------ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/regional/italy.js b/erpnext/accounts/doctype/sales_invoice/regional/italy.js index 0c20f364a4..2f305b914e 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/italy.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/italy.js @@ -1,7 +1,7 @@ frappe.ui.form.on("Sales Invoice", { refresh: (frm) => { if(frm.doc.docstatus == 1) { - frm.add_custom_button('Generate E-Invoice', () => { + frm.add_custom_button(__('Generate E-Invoice'), () => { frm.call({ method: "erpnext.regional.italy.utils.generate_single_invoice", args: { diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 149b32df12..6ef82971f5 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -1,9 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.crm"); - -cur_frm.email_field = "contact_email"; - erpnext.pre_sales.set_as_lost("Quotation"); erpnext.sales_common.setup_selling_controller(); @@ -22,6 +19,8 @@ frappe.ui.form.on("Opportunity", { } } }); + + frm.email_field = "contact_email"; }, validate: function(frm) { diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 7dbe485d29..54f0aadb1d 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -2,8 +2,6 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.buying"); - -// cur_frm.email_field = "contact_email"; // cur_frm.add_fetch('project', 'cost_center', 'cost_center'); erpnext.buying = { @@ -11,6 +9,7 @@ erpnext.buying = { erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController { setup() { super.setup(); + this.frm.email_field = "contact_email"; } onload(doc, cdt, cdn) { diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 430d9968e8..517d87127a 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -1,8 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -// cur_frm.email_field = "contact_email"; - frappe.provide("erpnext.selling"); erpnext.sales_common = { @@ -10,6 +8,7 @@ erpnext.sales_common = { erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController { setup() { super.setup(); + this.frm.email_field = "contact_email"; } onload() { @@ -61,7 +60,7 @@ erpnext.sales_common = { this.frm.set_query("item_code", "items", function() { return { query: "erpnext.controllers.queries.item_query", - filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer, 'has_variants': 0} + filters: {'is_sales_item': 1, 'customer': me.frm.doc.customer, 'has_variants': 0} } }); } @@ -260,9 +259,9 @@ erpnext.sales_common = { } set_product_bundle_help(doc) { - if(!cur_frm.fields_dict.packing_list) return; + if(!this.frm.fields_dict.packing_list) return; if ((doc.packed_items || []).length) { - $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true); + $(this.frm.fields_dict.packing_list.row.wrapper).toggle(true); if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { var help_msg = "
" + @@ -271,7 +270,7 @@ erpnext.sales_common = { frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = help_msg; } } else { - $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(false); + $(this.frm.fields_dict.packing_list.row.wrapper).toggle(false); if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = ''; } From efb51526a92158af34e43e5d4f62f2bca6e6ba6b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Jul 2023 14:00:20 +0530 Subject: [PATCH 100/107] fix: no default email account causing reposting issue --- .../doctype/repost_item_valuation/repost_item_valuation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 27066b825c..f128c8e206 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -271,7 +271,11 @@ def repost(doc): message += "
" + "Traceback:
" + traceback frappe.db.set_value(doc.doctype, doc.name, "error_log", message) - if not isinstance(e, RecoverableErrors): + outgoing_email_account = frappe.get_cached_value( + "Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name" + ) + + if outgoing_email_account and not isinstance(e, RecoverableErrors): notify_error_to_stock_managers(doc, message) doc.set_status("Failed") finally: From 3dd119eeea77313955d23a474b7321e1c5d83676 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 24 Jul 2023 14:50:11 +0530 Subject: [PATCH 101/107] refactor: apply terms based validation only on sales/purchase doctyp --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c175e2475d..596881a4e0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -210,7 +210,11 @@ class PaymentEntry(AccountsController): def term_based_allocation_enabled_for_reference( self, reference_doctype: str, reference_name: str ) -> bool: - if reference_doctype and reference_name: + if ( + reference_doctype + and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"] + and reference_name + ): if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"): return frappe.db.get_value( "Payment Terms Template", template, "allocate_payment_based_on_payment_terms" From 1436040d4c2fd5bf374a95addd6dc7b64548015d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 15:51:24 +0530 Subject: [PATCH 102/107] fix: allow both custodian and location while creating asset (copy #36263) (#36264) fix: allow both custodian and location while creating asset (cherry picked from commit bff00bc8b2c8911f1513a25f066077e2b0c0adf0) Co-authored-by: anandbaburajan --- .../doctype/asset_movement/asset_movement.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 22055dcb73..b85f7194f9 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -62,20 +62,21 @@ class AssetMovement(Document): frappe.throw(_("Source and Target Location cannot be same")) if self.purpose == "Receipt": - if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee): + if not (d.source_location) and not (d.target_location or d.to_employee): frappe.throw( _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) ) - elif d.from_employee and not d.target_location: - frappe.throw( - _("Target Location is required while receiving Asset {0} from an employee").format(d.asset) - ) - elif d.to_employee and d.target_location: - frappe.throw( - _( - "Asset {0} cannot be received at a location and given to an employee in a single movement" - ).format(d.asset) - ) + elif d.source_location: + if d.from_employee and not d.target_location: + frappe.throw( + _("Target Location is required while receiving Asset {0} from an employee").format(d.asset) + ) + elif d.to_employee and d.target_location: + frappe.throw( + _( + "Asset {0} cannot be received at a location and given to an employee in a single movement" + ).format(d.asset) + ) def validate_employee(self): for d in self.assets: From 34d7fb388d2743732dab7e56869f6006cdd1c8ea Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 24 Jul 2023 16:47:06 +0530 Subject: [PATCH 103/107] fix: customer filter in process soa --- .../process_statement_of_accounts.py | 2 +- ...ss_statement_of_accounts_accounts_receivable.html | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 08f4cf45d6..6193c849b5 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -140,7 +140,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency): def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, - "customer_name": entry.customer, + "customer": entry.customer, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, "sales_person": doc.sales_person if doc.sales_person else None, diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html index 07e1896292..259526f8c4 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -10,16 +10,12 @@

{{ _(report.report_name) }}

- {% if (filters.customer_name) %} - {{ filters.customer_name }} - {% else %} - {{ filters.customer ~ filters.supplier }} - {% endif %} + {{ filters.customer }}

- {% if (filters.tax_id) %} - {{ _("Tax Id: ") }}{{ filters.tax_id }} - {% endif %} + {% if (filters.tax_id) %} + {{ _("Tax Id: ") }}{{ filters.tax_id }} + {% endif %}
{{ _(filters.ageing_based_on) }} From 6f376cf103cfeca06b37e4523f78b0ced32143f4 Mon Sep 17 00:00:00 2001 From: DaizyModi Date: Mon, 24 Jul 2023 18:02:42 +0530 Subject: [PATCH 104/107] fix: remove unused params --- erpnext/controllers/taxes_and_totals.py | 28 +++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 04d5e1c69a..fac713de3e 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -957,7 +957,7 @@ def get_itemised_tax_breakup_html(doc): # get tax breakup data itemised_tax_data = get_itemised_tax_breakup_data(doc) - get_rounded_tax_amount(itemised_tax_data, doc.precision("tax_amount", "taxes"), tax_accounts) + get_rounded_tax_amount(itemised_tax_data, doc.precision("tax_amount", "taxes")) update_itemised_tax_data(doc) frappe.flags.company = None @@ -1000,21 +1000,17 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts): @erpnext.allow_regional def get_itemised_tax_breakup_data(doc): - return _get_itemised_tax_breakup_data(doc) - - -def _get_itemised_tax_breakup_data(doc, with_tax_account=False): - itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=with_tax_account) + itemised_tax = get_itemised_tax(doc.taxes) itemised_taxable_amount = get_itemised_taxable_amount(doc.items) itemised_tax_data = [] for item_code, taxes in itemised_tax.items(): - for _item_code, taxable_amount in itemised_taxable_amount.items(): - if item_code == _item_code: - itemised_tax_data.append( - frappe._dict({"item": item_code, "taxable_amount": taxable_amount, **taxes}) - ) + itemised_tax_data.append( + frappe._dict( + {"item": item_code, "taxable_amount": itemised_taxable_amount.get(item_code), **taxes} + ) + ) return itemised_tax_data @@ -1059,12 +1055,12 @@ def get_itemised_taxable_amount(items): return itemised_taxable_amount -def get_rounded_tax_amount(itemised_tax, precision, tax_accounts): +def get_rounded_tax_amount(itemised_tax, precision): # Rounding based on tax_amount precision - for _itemised_tax in itemised_tax: - for key, value in _itemised_tax.items(): - if key in tax_accounts: - value["tax_amount"] = flt(value["tax_amount"], precision) + for taxes in itemised_tax: + for row in taxes.values(): + if isinstance(row, dict) and isinstance(row["tax_amount"], float): + row["tax_amount"] = flt(row["tax_amount"], precision) class init_landed_taxes_and_totals(object): From 4205f564a06e8b074f1b0a2f4dda38bcef3967f2 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 24 Jul 2023 18:37:36 +0530 Subject: [PATCH 105/107] fix(regional): set `frappe.flags.company` temporarily, where required --- erpnext/accounts/party.py | 6 +++--- erpnext/controllers/accounts_controller.py | 5 ++++- erpnext/controllers/taxes_and_totals.py | 22 ++++++++-------------- erpnext/utilities/regional.py | 13 +++++++++++++ 4 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 erpnext/utilities/regional.py diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 03cf82a2b0..4996203635 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -33,6 +33,7 @@ import erpnext from erpnext import get_company_currency from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen +from erpnext.utilities.regional import temporary_flag PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"} SALES_TRANSACTION_TYPES = { @@ -261,9 +262,8 @@ def set_address_details( ) if doctype in TRANSACTION_TYPES: - # required to set correct region - frappe.flags.company = company - get_regional_address_details(party_details, doctype, company) + with temporary_flag("company", company): + get_regional_address_details(party_details, doctype, company) return party_address, shipping_address diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4193b5327d..79404894cd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -56,6 +56,7 @@ from erpnext.stock.get_item_details import ( get_item_tax_map, get_item_warehouse, ) +from erpnext.utilities.regional import temporary_flag from erpnext.utilities.transaction_base import TransactionBase @@ -760,7 +761,9 @@ class AccountsController(TransactionBase): } ) - update_gl_dict_with_regional_fields(self, gl_dict) + with temporary_flag("company", self.company): + update_gl_dict_with_regional_fields(self, gl_dict) + accounting_dimensions = get_accounting_dimensions() dimension_dict = frappe._dict() diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 4661c5ca7e..77006b905c 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import ( validate_taxes_and_charges, ) from erpnext.stock.get_item_details import _get_item_tax_template +from erpnext.utilities.regional import temporary_flag class calculate_taxes_and_totals(object): @@ -942,7 +943,6 @@ class calculate_taxes_and_totals(object): def get_itemised_tax_breakup_html(doc): if not doc.taxes: return - frappe.flags.company = doc.company # get headers tax_accounts = [] @@ -952,15 +952,11 @@ def get_itemised_tax_breakup_html(doc): if tax.description not in tax_accounts: tax_accounts.append(tax.description) - headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) - - # get tax breakup data - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) - - get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) - - update_itemised_tax_data(doc) - frappe.flags.company = None + with temporary_flag("company", doc.company): + headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) + itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) + get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) + update_itemised_tax_data(doc) return frappe.render_template( "templates/includes/itemised_tax_breakup.html", @@ -977,10 +973,8 @@ def get_itemised_tax_breakup_html(doc): @frappe.whitelist() def get_round_off_applicable_accounts(company, account_list): # required to set correct region - frappe.flags.company = company - account_list = get_regional_round_off_accounts(company, account_list) - - return account_list + with temporary_flag("company", company): + return get_regional_round_off_accounts(company, account_list) @erpnext.allow_regional diff --git a/erpnext/utilities/regional.py b/erpnext/utilities/regional.py new file mode 100644 index 0000000000..858976f855 --- /dev/null +++ b/erpnext/utilities/regional.py @@ -0,0 +1,13 @@ +from contextlib import contextmanager + +import frappe + + +@contextmanager +def temporary_flag(flag_name, value): + flags = frappe.local.flags + flags[flag_name] = value + try: + yield + finally: + flags.pop(flag_name, None) From 1bc87a970a9951d7c12586066e318440edac076e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:37:07 +0530 Subject: [PATCH 106/107] fix: set new purchase_receipt_amount on asset split (copy #36272) (#36280) fix: set new purchase_receipt_amount on asset split (cherry picked from commit 7fd9b489eed33a81f29ecb3802d8a07d232c4c9c) Co-authored-by: anandbaburajan --- erpnext/assets/doctype/asset/asset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 42f531189a..7dc438e08f 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -933,6 +933,8 @@ def create_new_asset_after_split(asset, split_qty): ) new_asset.gross_purchase_amount = new_gross_purchase_amount + if asset.purchase_receipt_amount: + new_asset.purchase_receipt_amount = new_gross_purchase_amount new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation new_asset.asset_quantity = split_qty new_asset.split_from = asset.name From 62ca89b10f0cefeb0a4fc0d22b5d4e7b0bcdbc57 Mon Sep 17 00:00:00 2001 From: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> Date: Tue, 25 Jul 2023 13:01:10 +0530 Subject: [PATCH 107/107] fix: POS background color optimized in dark mode (#36287) fix: POS dark theme compatability Co-authored-by: Ritvik Sardana --- erpnext/public/scss/point-of-sale.scss | 30 +++++++++++--------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 7b7530b150..c9d001c127 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -34,7 +34,7 @@ } .abbr { - background-color: var(--gray-50); + background-color: var(--control-bg); font-size: var(--text-3xl); } @@ -72,7 +72,7 @@ .highlighted-numpad-btn { box-shadow: inset 0 0px 4px 0px rgba(0, 0, 0, 0.15) !important; font-weight: 700; - background-color: var(--gray-50); + background-color: var(--control-bg); } > .items-selector { @@ -152,7 +152,6 @@ margin-bottom: 0px; min-height: 8rem; height: 8rem; - color: var(--gray-500); > img { @extend .image; @@ -242,7 +241,7 @@ width: 3rem; height: 3rem; border-radius: 50%; - color: var(--gray-500); + color: var(--text-light); margin-right: var(--margin-md); > img { @@ -268,7 +267,6 @@ } >.customer-desc { - color: var(--gray-600); font-weight: 500; font-size: var(--text-sm); } @@ -363,7 +361,7 @@ display: flex; align-items: center; justify-content: center; - background-color: var(--gray-50); + background-color: var(--control-bg); border-radius: var(--border-radius-md); font-size: var(--text-md); font-weight: 500; @@ -385,7 +383,7 @@ border-radius: var(--border-radius-md); &:hover { - background-color: var(--gray-50); + background-color: var(--control-bg); } > .item-image { @@ -395,7 +393,7 @@ width: 2rem; height: 2rem; border-radius: var(--border-radius-md); - color: var(--gray-500); + color: var(--text-light); margin-right: var(--margin-md); > img { @@ -537,13 +535,13 @@ > .edit-cart-btn { @extend .primary-action; display: none; - background-color: var(--gray-300); + background-color: var(--control-bg); font-weight: 500; transition: all 0.15s ease-in-out; &:hover { - background-color: var(--gray-600); - color: white; + background-color: var(--control-bg); + color: var(--text-light); font-weight: 700; } } @@ -832,13 +830,13 @@ > .shortcut { @extend .pointer-no-select; border-radius: var(--border-radius-sm); - background-color: var(--gray-100); + background-color: var(--control-bg); font-weight: 500; padding: var(--padding-xs) var(--padding-sm); transition: all 0.15s ease-in-out; &:hover { - background-color: var(--gray-300); + background-color: var(--control-bg); } } } @@ -912,7 +910,7 @@ > .totals { display: flex; - background-color: var(--gray-100); + background-color: var(--control-bg); justify-content: center; padding: var(--padding-md); border-radius: var(--border-radius-md); @@ -924,7 +922,6 @@ > .total-label { font-size: var(--text-md); font-weight: 500; - color: var(--gray-600); } > .value { @@ -1036,7 +1033,6 @@ > .customer-email { font-size: var(--text-md); font-weight: 500; - color: var(--gray-600); } > .cashier { @@ -1071,7 +1067,7 @@ display: flex; flex-direction: column; border-radius: var(--border-radius-md); - background-color: var(--gray-50); + background-color: var(--control-bg); margin: var(--margin-md) 0px; > .summary-row-wrapper {
{{ item }}{{ taxes.item }} {% if doc.get('is_return') %} - {{ frappe.utils.fmt_money((itemised_taxable_amount.get(item, 0))|abs, None, doc.currency) }} + {{ frappe.utils.fmt_money(taxes.taxable_amount |abs, None, doc.currency) }} {% else %} - {{ frappe.utils.fmt_money(itemised_taxable_amount.get(item, 0), None, doc.currency) }} + {{ frappe.utils.fmt_money(taxes.taxable_amount, None, doc.currency) }} {% endif %}