diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 289c19a5885..d60f77120b2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2376,9 +2376,7 @@ def get_outstanding_reference_documents(args, validate=False): vouchers=args.get("vouchers") or None, ) - outstanding_invoices = split_invoices_based_on_payment_terms( - outstanding_invoices, args.get("company") - ) + outstanding_invoices = split_refdocs_based_on_payment_terms(outstanding_invoices, args.get("company")) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -2416,6 +2414,8 @@ def get_outstanding_reference_documents(args, validate=False): filters=args, ) + orders_to_be_billed = split_refdocs_based_on_payment_terms(orders_to_be_billed, args.get("company")) + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: @@ -2438,13 +2438,13 @@ def get_outstanding_reference_documents(args, validate=False): return data -def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list: +def split_refdocs_based_on_payment_terms(refdocs, company) -> list: """Split a list of invoices based on their payment terms.""" - exc_rates = get_currency_data(outstanding_invoices, company) + exc_rates = get_currency_data(refdocs, company) - outstanding_invoices_after_split = [] - for entry in outstanding_invoices: - if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]: + outstanding_refdoc_after_split = [] + for entry in refdocs: + if entry.voucher_type in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"]: if payment_term_template := frappe.db.get_value( entry.voucher_type, entry.voucher_no, "payment_terms_template" ): @@ -2459,25 +2459,25 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list ), alert=True, ) - outstanding_invoices_after_split += split_rows + outstanding_refdoc_after_split += split_rows continue # If not an invoice or no payment terms template, add as it is - outstanding_invoices_after_split.append(entry) + outstanding_refdoc_after_split.append(entry) - return outstanding_invoices_after_split + return outstanding_refdoc_after_split -def get_currency_data(outstanding_invoices: list, company: str | None = None) -> dict: +def get_currency_data(outstanding_refdocs: list, company: str | None = 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 - for doctype in ["Sales Invoice", "Purchase Invoice"]: - invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] + for doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"]: + refdoc = [x.voucher_no for x in outstanding_refdocs if x.voucher_type == doctype] for x in frappe.db.get_all( doctype, - filters={"name": ["in", invoices]}, + filters={"name": ["in", refdoc]}, fields=["name", "currency", "conversion_rate", "party_account_currency"], ): exc_rates[x.name] = frappe._dict( diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 12f5276fbfb..9d2890f5e79 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -2019,6 +2019,92 @@ class TestPaymentEntry(ERPNextTestSuite): self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, pe.doctype, pe.name) self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, "Journal Entry", jv[0]) + def test_outstanding_orders_split_by_payment_terms(self): + create_payment_terms_template() + + so = make_sales_order(do_not_save=1, qty=1, rate=200) + so.payment_terms_template = "Test Receivable Template" + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 2) + self.assertEqual(references[0].voucher_no, so.name) + self.assertEqual(references[1].voucher_no, so.name) + self.assertEqual(references[0].payment_term, "Basic Amount Receivable") + self.assertEqual(references[1].payment_term, "Tax Receivable") + + def test_outstanding_orders_no_split_when_allocate_disabled(self): + create_payment_terms_template() + + template = frappe.get_doc("Payment Terms Template", "Test Receivable Template") + template.allocate_payment_based_on_payment_terms = 0 + template.save() + + so = make_sales_order(do_not_save=1, qty=1, rate=200) + so.payment_terms_template = "Test Receivable Template" + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 1) + self.assertIsNone(references[0].payment_term) + + template.allocate_payment_based_on_payment_terms = 1 + template.save() + + def test_outstanding_multicurrency_sales_order_split(self): + create_payment_terms_template() + + so = make_sales_order( + customer="_Test Customer USD", + currency="USD", + qty=1, + rate=100, + do_not_submit=True, + ) + so.payment_terms_template = "Test Receivable Template" + so.conversion_rate = 50 + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + # Should split without throwing currency errors + self.assertEqual(len(references), 2) + for ref in references: + self.assertEqual(ref.voucher_no, so.name) + self.assertIsNotNone(ref.payment_term) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry")