From 6bd56d2d5f62814eb2f777934ed894bc5709e947 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 1 Nov 2023 20:11:00 +0530 Subject: [PATCH] refactor: `split_invoices_based_on_payment_terms` - Invoices were in the wrong order due to the logic. The invoices with payment terms were added first and the rest after. - Overly long function with unnecessary loops (reduced to one main loop) and complexity - The split row as per payment terms was not ordered. So the second installment was allocated first --- .../doctype/payment_entry/payment_entry.py | 145 ++++++++++-------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e6403fddef..fc6e1f462e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1698,13 +1698,42 @@ def get_outstanding_reference_documents(args, validate=False): return data -def split_invoices_based_on_payment_terms(outstanding_invoices, company): - invoice_ref_based_on_payment_terms = {} +def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list: + """Split a list of invoices based on their payment terms.""" + exc_rates = get_currency_data(outstanding_invoices, company) + outstanding_invoices_after_split = [] + for entry in outstanding_invoices: + if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]: + if payment_term_template := frappe.db.get_value( + entry.voucher_type, entry.voucher_no, "payment_terms_template" + ): + split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates) + if not split_rows: + continue + + frappe.msgprint( + _("Spliting {} {} into {} row(s) as per Payment Terms").format( + split_rows[0]["voucher_type"], split_rows[0]["voucher_no"], len(split_rows) + ), + alert=True, + ) + outstanding_invoices_after_split += split_rows + continue + + # If not an invoice or no payment terms template, add as it is + outstanding_invoices_after_split.append(entry) + + return outstanding_invoices_after_split + + +def get_currency_data(outstanding_invoices: list, company: str = None) -> dict: + """Get currency and conversion data for a list of invoices.""" + exc_rates = frappe._dict() 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( @@ -1719,72 +1748,54 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company): 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( - d.voucher_type, d.voucher_no, "payment_terms_template" + return exc_rates + + +def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list: + """Split invoice based on its payment schedule table.""" + split_rows = [] + allocate_payment_based_on_payment_terms = frappe.db.get_value( + "Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms" + ) + + if not allocate_payment_based_on_payment_terms: + return [invoice] + + payment_schedule = frappe.get_all( + "Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date" + ) + for payment_term in payment_schedule: + if not payment_term.outstanding > 0.1: + continue + + 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) + + split_rows.append( + frappe._dict( + { + "due_date": invoice.due_date, + "currency": invoice.currency, + "voucher_no": invoice.voucher_no, + "voucher_type": invoice.voucher_type, + "posting_date": invoice.posting_date, + "invoice_amount": flt(invoice.invoice_amount), + "outstanding_amount": payment_term_outstanding + if payment_term_outstanding + else invoice.outstanding_amount, + "payment_term_outstanding": payment_term_outstanding, + "payment_amount": payment_term.payment_amount, + "payment_term": payment_term.payment_term, + } ) - if payment_term_template: - allocate_payment_based_on_payment_terms = frappe.get_cached_value( - "Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms" - ) - if allocate_payment_based_on_payment_terms: - payment_schedule = frappe.get_all( - "Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"] - ) + ) - 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( - { - "due_date": d.due_date, - "currency": d.currency, - "voucher_no": d.voucher_no, - "voucher_type": d.voucher_type, - "posting_date": d.posting_date, - "invoice_amount": flt(d.invoice_amount), - "outstanding_amount": payment_term_outstanding - if payment_term_outstanding - else d.outstanding_amount, - "payment_term_outstanding": payment_term_outstanding, - "payment_amount": payment_term.payment_amount, - "payment_term": payment_term.payment_term, - "account": d.account, - } - ) - ) - - outstanding_invoices_after_split = [] - if invoice_ref_based_on_payment_terms: - for idx, ref in invoice_ref_based_on_payment_terms.items(): - voucher_no = ref[0]["voucher_no"] - voucher_type = ref[0]["voucher_type"] - - frappe.msgprint( - _("Spliting {} {} into {} row(s) as per Payment Terms").format( - voucher_type, voucher_no, len(ref) - ), - alert=True, - ) - - outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx] - - existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices)) - index = outstanding_invoices.index(existing_row[0]) - outstanding_invoices.pop(index) - - outstanding_invoices_after_split += outstanding_invoices - return outstanding_invoices_after_split + return split_rows def get_orders_to_be_billed(