Merge pull request #57539 from harisansari008/fix/dunning-timestamp-mismatch-multi-installment

fix: prevent TimestampMismatchError resolving Dunning with multiple overdue installments
This commit is contained in:
Khushi Rawat
2026-08-04 13:02:44 +05:30
committed by GitHub
2 changed files with 36 additions and 0 deletions

View File

@@ -275,6 +275,7 @@ def get_linked_dunnings_as_per_state(sales_invoice, state):
.join(overdue_payment)
.on(overdue_payment.parent == dunning.name)
.select(dunning.name)
.distinct()
.where(
(dunning.status == state)
& (dunning.docstatus != 2)

View File

@@ -123,6 +123,41 @@ class TestDunning(ERPNextTestSuite):
self.assertEqual(sales_invoice.status, "Overdue")
self.assertEqual(dunning.status, "Unresolved")
def test_payment_against_invoice_with_multiple_overdue_installments_in_dunning(self):
"""
When an invoice has more than one overdue installment, its Dunning holds one
Overdue Payment row per installment. Submitting a Payment Entry for the invoice
must resolve the Dunning without raising a TimestampMismatchError caused by the
same Dunning being loaded and saved more than once.
"""
create_payment_terms_template_for_dunning()
# Post far enough in the past that BOTH installments (5 and 10 credit days) are overdue.
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -15),
qty=1,
rate=100,
do_not_submit=True,
)
sales_invoice.payment_terms_template = "_Test 50-50 for Dunning"
sales_invoice.submit()
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
# Two overdue installments -> two overdue payment rows for the same invoice.
self.assertEqual(len(dunning.overdue_payments), 2)
dunning.submit()
self.assertEqual(dunning.status, "Unresolved")
# Pay the invoice in full. This previously raised TimestampMismatchError on the Dunning.
pe = get_payment_entry("Sales Invoice", sales_invoice.name)
pe.reference_no, pe.reference_date = "3", nowdate()
pe.insert()
pe.submit()
sales_invoice.reload()
dunning.reload()
self.assertEqual(sales_invoice.outstanding_amount, 0)
self.assertEqual(dunning.status, "Resolved")
def test_dunning_resolution_from_credit_note(self):
"""
Test that dunning is resolved when a credit note is issued against the original invoice.