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 01/60] 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 02/60] 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 03/60] 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 04/60] 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 05/60] 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 06/60] 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 07/60] 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 08/60] 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 09/60] 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 10/60] 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 11/60] 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 12/60] 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 13/60] 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 14/60] 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 15/60] 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 16/60] 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 17/60] 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 18/60] 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 19/60] 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 20/60] 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 21/60] 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 22/60] 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 {{_(\\\"Description\\\")}} | \\n\\t {{_(\\\"Amount\\\")}} | \\n
\\n \\n \\n {{_(\\\"Outstanding Amount\\\")}}\\n | \\n \\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n | \\n
\\n {%if doc.rate_of_interest > 0%}\\n \\n \\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n | \\n \\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n | \\n
\\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n \\n \\n {{_(\\\"Dunning Fee\\\")}}\\n | \\n \\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n | \\n
\\n {% endif %}\\n \\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 23/60] 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 24/60] 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 25/60] 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 26/60] 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 27/60] 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 28/60] 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 29/60] 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 30/60] 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 31/60] 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 32/60] 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 33/60] 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 34/60] 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 35/60] 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 36/60] 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 37/60] 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 38/60] 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 39/60] 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 40/60] 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 41/60] 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 42/60] 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 43/60] 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 44/60] 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 45/60] 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 46/60] 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 47/60] 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 48/60] 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 49/60] 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 50/60] 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 51/60] 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 52/60] 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 53/60] 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 54/60] 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 55/60] 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 56/60] 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 57/60] 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 58/60] 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 59/60] 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 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 60/60] 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,