From f8d4b19cb987b267dbd18d6ef1ab52426d7f51d1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Jul 2023 17:36:38 +0530 Subject: [PATCH 1/2] fix: broken overallocation validation in payment entry In a multi term payment schedule, overallocation logic broke. Fixing it using individual term outstanding amount in references. this should work for the simple, one term payment schedule as well --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index dcd7295bae..325d0f3cda 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -226,10 +226,12 @@ class PaymentEntry(AccountsController): latest_lookup = {} for d in latest_references: d = frappe._dict(d) - latest_lookup.update({(d.voucher_type, d.voucher_no): d}) + latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d for d in self.get("references"): - latest = latest_lookup.get((d.reference_doctype, d.reference_name)) + latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get( + d.payment_term + ) # The reference has already been fully paid if not latest: @@ -251,6 +253,18 @@ class PaymentEntry(AccountsController): if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): frappe.throw(fail_message.format(d.idx)) + if d.payment_term and ( + (flt(d.allocated_amount)) > 0 + and flt(d.allocated_amount) > flt(latest.payment_term_outstanding) + ): + frappe.throw( + _( + "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" + ).format( + d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term + ) + ) + # Check for negative outstanding invoices as well if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): frappe.throw(fail_message.format(d.idx)) @@ -1589,6 +1603,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), + "payment_term_outstanding": flt(payment_term.outstanding), "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, "account": d.account, @@ -2371,6 +2386,7 @@ def get_reference_as_per_payment_terms( "due_date": doc.get("due_date"), "total_amount": grand_total, "outstanding_amount": outstanding_amount, + "payment_term_outstanding": payment_term_outstanding, "payment_term": payment_term.payment_term, "allocated_amount": payment_term_outstanding, } From ee83f94bb0ad22e487d63e41cb878ffc27f974fc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Jul 2023 09:26:39 +0530 Subject: [PATCH 2/2] refactor: payment term outstanding in party account currency --- .../doctype/payment_entry/payment_entry.py | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 325d0f3cda..e9a3b79acb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1514,7 +1514,9 @@ def get_outstanding_reference_documents(args, validate=False): accounting_dimensions=accounting_dimensions_filter, ) - outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + outstanding_invoices = split_invoices_based_on_payment_terms( + outstanding_invoices, args.get("company") + ) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -1574,8 +1576,27 @@ def get_outstanding_reference_documents(args, validate=False): return data -def split_invoices_based_on_payment_terms(outstanding_invoices): +def split_invoices_based_on_payment_terms(outstanding_invoices, company): invoice_ref_based_on_payment_terms = {} + + company_currency = ( + frappe.db.get_value("Company", company, "default_currency") if company else None + ) + exc_rates = frappe._dict() + for doctype in ["Sales Invoice", "Purchase Invoice"]: + invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] + for x in frappe.db.get_all( + doctype, + filters={"name": ["in", invoices]}, + fields=["name", "currency", "conversion_rate", "party_account_currency"], + ): + exc_rates[x.name] = frappe._dict( + conversion_rate=x.conversion_rate, + currency=x.currency, + party_account_currency=x.party_account_currency, + company_currency=company_currency, + ) + for idx, d in enumerate(outstanding_invoices): if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]: payment_term_template = frappe.db.get_value( @@ -1592,6 +1613,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): for payment_term in payment_schedule: if payment_term.outstanding > 0.1: + doc_details = exc_rates.get(payment_term.parent, None) + is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and ( + doc_details.party_account_currency != doc_details.company_currency + ) + payment_term_outstanding = flt(payment_term.outstanding) + if not is_multi_currency_acc: + payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding) + invoice_ref_based_on_payment_terms.setdefault(idx, []) invoice_ref_based_on_payment_terms[idx].append( frappe._dict( @@ -1603,7 +1632,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), - "payment_term_outstanding": flt(payment_term.outstanding), + "payment_term_outstanding": payment_term_outstanding, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, "account": d.account,