From ac8b3f18c79efb3056c5442118a13e12bd383edb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 22 Jun 2026 14:13:18 +0530 Subject: [PATCH 1/2] test: add purchase-side Payment Entry allocation coverage - pay multiple purchase invoices with a single Payment Entry - unallocated (advance) amount when a supplier payment is overpaid - allocating more than a purchase invoice's outstanding amount is rejected --- .../payment_entry/test_payment_entry.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index c8e096e65ac..a24b8fad1bf 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -246,6 +246,47 @@ class TestPaymentEntry(ERPNextTestSuite): outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount")) self.assertEqual(outstanding_amount, 0) + def test_pay_multiple_purchase_invoices_in_one_entry(self): + pi1 = make_purchase_invoice() # outstanding 250 + pi2 = make_purchase_invoice() # outstanding 250 + + pe = get_payment_entry("Purchase Invoice", pi1.name, bank_account="_Test Cash - _TC") + pe.append( + "references", + { + "reference_doctype": "Purchase Invoice", + "reference_name": pi2.name, + "total_amount": pi2.grand_total, + "outstanding_amount": pi2.outstanding_amount, + "allocated_amount": pi2.outstanding_amount, + }, + ) + pe.paid_amount = pe.references[0].allocated_amount + pe.references[1].allocated_amount + pe.insert() + pe.submit() + + self.assertEqual(pe.total_allocated_amount, 500) + self.assertEqual(frappe.db.get_value("Purchase Invoice", pi1.name, "outstanding_amount"), 0) + self.assertEqual(frappe.db.get_value("Purchase Invoice", pi2.name, "outstanding_amount"), 0) + + def test_unallocated_amount_on_overpaid_purchase_payment(self): + pi = make_purchase_invoice() # outstanding 250 + + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC") + pe.paid_amount = pe.references[0].allocated_amount + 200 # overpay -> 200 advance + pe.received_amount = pe.paid_amount + pe.insert() + + self.assertEqual(pe.unallocated_amount, 200) + + def test_overallocation_against_purchase_invoice_throws(self): + pi = make_purchase_invoice() # outstanding 250 + + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC") + pe.references[0].allocated_amount += 100 # 350 > 250 outstanding + pe.paid_amount = pe.references[0].allocated_amount + self.assertRaises(frappe.ValidationError, pe.insert) + def test_payment_against_sales_invoice_to_check_status(self): si = create_sales_invoice( customer="_Test Customer USD", From 7d8d1eaec788ff7002895ce5745550f51ecae259 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2026 14:15:38 +0530 Subject: [PATCH 2/2] test: submit overpaid payment for GL coverage and sync received_amount --- .../payment_entry/test_payment_entry.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index a24b8fad1bf..873d1f88632 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -261,7 +261,9 @@ class TestPaymentEntry(ERPNextTestSuite): "allocated_amount": pi2.outstanding_amount, }, ) - pe.paid_amount = pe.references[0].allocated_amount + pe.references[1].allocated_amount + pe.paid_amount = pe.received_amount = ( + pe.references[0].allocated_amount + pe.references[1].allocated_amount + ) pe.insert() pe.submit() @@ -276,15 +278,28 @@ class TestPaymentEntry(ERPNextTestSuite): pe.paid_amount = pe.references[0].allocated_amount + 200 # overpay -> 200 advance pe.received_amount = pe.paid_amount pe.insert() + pe.submit() + self.assertEqual(pe.docstatus, 1) self.assertEqual(pe.unallocated_amount, 200) + # end-to-end: submitting posts a balanced GL for the full paid amount (250 + # settling the invoice + 200 advance) + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + fields=["debit", "credit"], + ) + self.assertTrue(gl_entries, "Submitted payment produced no GL entries") + self.assertEqual(flt(sum(e.debit for e in gl_entries)), flt(sum(e.credit for e in gl_entries))) + self.assertEqual(flt(sum(e.debit for e in gl_entries)), 450) + def test_overallocation_against_purchase_invoice_throws(self): pi = make_purchase_invoice() # outstanding 250 pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC") pe.references[0].allocated_amount += 100 # 350 > 250 outstanding - pe.paid_amount = pe.references[0].allocated_amount + pe.paid_amount = pe.received_amount = pe.references[0].allocated_amount self.assertRaises(frappe.ValidationError, pe.insert) def test_payment_against_sales_invoice_to_check_status(self):