From 14600fa19007e3a00bc2d987f04f2a7b29d99536 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 11:18:11 +0530 Subject: [PATCH 1/3] fix: allocation logic on 'Get Outstanding Invoices' btn in PE 1. fixed broken `payment_term` filter in Payment References section 2. Throw error if user fails to select 'Payment Term' for an invoice with 'Payment Term based allocation' enabled. (cherry picked from commit 662ccd467c94e66c866d8646d29104846efb936a) --- .../doctype/payment_entry/payment_entry.js | 7 ++----- .../doctype/payment_entry/payment_entry.py | 18 ++++++++++++++---- erpnext/controllers/queries.py | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index ace34e052cd..f91d29a6b68 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -122,13 +122,10 @@ frappe.ui.form.on('Payment Entry', { frm.set_query('payment_term', 'references', function(frm, cdt, cdn) { const child = locals[cdt][cdn]; if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) { - let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name}); - - payment_term_list = payment_term_list.map(pt => pt.payment_term); - return { + query: "erpnext.controllers.queries.get_payment_terms_for_references", filters: { - 'name': ['in', payment_term_list] + 'reference': child.reference_name } } } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f29be181e7b..e8d88ba1381 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -184,10 +184,17 @@ class PaymentEntry(AccountsController): d = frappe._dict(d) latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d - for d in self.get("references"): - latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get( - d.payment_term - ) + for idx, d in enumerate(self.get("references"), start=1): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() + + if (d.payment_term is None or d.payment_term == "") and d.payment_term not in latest.keys(): + frappe.throw( + _( + "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" + ).format(frappe.bold(d.reference_name), frappe.bold(idx)) + ) + + latest = latest.get(d.payment_term) # The reference has already been fully paid if not latest: @@ -1510,6 +1517,9 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company): "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), "payment_term_outstanding": payment_term_outstanding, + "allocated_amount": payment_term_outstanding + if payment_term_outstanding + else d.outstanding_amount, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, } diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 80bc3eef745..36225e3dd5e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -823,3 +823,18 @@ def get_fields(doctype, fields=None): fields.insert(1, meta.title_field.strip()) return unique(fields) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, filters) -> list: + terms = [] + if filters: + terms = frappe.db.get_all( + "Payment Schedule", + filters={"parent": filters.get("reference")}, + fields=["payment_term"], + limit=page_len, + as_list=1, + ) + return terms From 690b52622d62ddefb2d06817d4ec6cefda30ce94 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 23 Jul 2023 11:55:16 +0530 Subject: [PATCH 2/3] refactor: handle references without any template and `payment_term` (cherry picked from commit ec7558b9e043ec90c5492e2d8581a750b76e88af) --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e8d88ba1381..5000bce35d5 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -164,6 +164,16 @@ class PaymentEntry(AccountsController): if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): frappe.throw(fail_message.format(d.idx)) + def term_based_allocation_enabled_for_reference( + self, reference_doctype: str, reference_name: str + ) -> bool: + if reference_doctype and reference_name: + if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"): + return frappe.db.get_value( + "Payment Terms Template", template, "allocate_payment_based_on_payment_terms" + ) + return False + def validate_allocated_amount_with_latest_data(self): latest_references = get_outstanding_reference_documents( { @@ -187,14 +197,20 @@ class PaymentEntry(AccountsController): for idx, d in enumerate(self.get("references"), start=1): latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() - if (d.payment_term is None or d.payment_term == "") and d.payment_term not in latest.keys(): + # If term based allocation is enabled, throw + if ( + d.payment_term is None or d.payment_term == "" + ) and self.term_based_allocation_enabled_for_reference( + d.reference_doctype, d.reference_name + ): frappe.throw( _( "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" ).format(frappe.bold(d.reference_name), frappe.bold(idx)) ) - latest = latest.get(d.payment_term) + # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key + latest = latest.get(d.payment_term) or latest.get(None) # The reference has already been fully paid if not latest: From 8f24292155c0112b0f5d2a394e6fbf1e8d138db3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 24 Jul 2023 14:13:57 +0530 Subject: [PATCH 3/3] fix: apply terms validaton only in Sales/Purchase documents --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5000bce35d5..411ec6d02c6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -167,7 +167,11 @@ class PaymentEntry(AccountsController): def term_based_allocation_enabled_for_reference( self, reference_doctype: str, reference_name: str ) -> bool: - if reference_doctype and reference_name: + if ( + reference_doctype + and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"] + and reference_name + ): if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"): return frappe.db.get_value( "Payment Terms Template", template, "allocate_payment_based_on_payment_terms"