diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 5bbe02e4a01..47ada82755e 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -21,7 +21,12 @@ def execute(filters=None): entries = get_entries(filters) invoice_details = get_invoice_posting_date_map(filters) - report = ReceivablePayableReport(filters) + # Only four range columns are defined (range1-range4, the last being "90 Above"). + # Three thresholds yield exactly four buckets, so payments more than 90 days after + # the invoice land in range4 instead of an unread range5. + report_filters = frappe._dict(filters) + report_filters.range = "30, 60, 90" + report = ReceivablePayableReport(report_filters) data = [] for d in entries: diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/test_payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/test_payment_period_based_on_invoice_date.py index 2c88a2c1171..f7c4f874c25 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/test_payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/test_payment_period_based_on_invoice_date.py @@ -32,15 +32,15 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite): } ) filters.update(extra) - return execute(filters) + columns, data = execute(filters) + fieldnames = [c["fieldname"] for c in columns] + # Map each positional row to a dict keyed by column fieldname so assertions + # stay correct even if a column is inserted or reordered. + return columns, [dict(zip(fieldnames, row, strict=False)) for row in data] def find_payment_row(self, data, payment_name): - # Row shape (positional): payment_document, payment_entry(voucher_no), - # party_type, party, posting_date, invoice(against_voucher_no), - # invoice_posting_date, due_date, amount, remarks, age, - # range1, range2, range3, range4, [delay_in_payment] for row in data: - if row[1] == payment_name: + if row["payment_entry"] == payment_name: return row return None @@ -57,42 +57,60 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite): invoice = create_sales_invoice(customer="_Test Customer", rate=1000, posting_date="2026-06-01") payment = self.pay_invoice(invoice, "2026-06-20") - columns, data = self.run_report() + _columns, data = self.run_report() row = self.find_payment_row(data, payment.name) self.assertIsNotNone(row, "Payment row not found in report output") - # Positional assertions on the row shape. - self.assertEqual(row[2], "Customer") - self.assertEqual(row[4], getdate("2026-06-20")) # payment posting date - self.assertEqual(row[5], invoice.name) # against invoice - self.assertEqual(row[6], getdate("2026-06-01")) # invoice posting date - self.assertEqual(row[8], 1000) # amount - self.assertEqual(row[10], 19) # age = payment date - invoice date + self.assertEqual(row["party_type"], "Customer") + self.assertEqual(row["posting_date"], getdate("2026-06-20")) + self.assertEqual(row["invoice"], invoice.name) + self.assertEqual(row["invoice_posting_date"], getdate("2026-06-01")) + self.assertEqual(row["amount"], 1000) + self.assertEqual(row["age"], 19) # age = payment date - invoice date # Buckets: 0-30 filled, others empty. - self.assertEqual(row[11], 1000) # range1 (0-30) - self.assertEqual(row[12], 0) # range2 (30-60) - self.assertEqual(row[13], 0) # range3 (60-90) - self.assertEqual(row[14], 0) # range4 (90 Above) + self.assertEqual(row["range1"], 1000) # 0-30 + self.assertEqual(row["range2"], 0) # 30-60 + self.assertEqual(row["range3"], 0) # 60-90 + self.assertEqual(row["range4"], 0) # 90 Above def test_paid_amount_lands_in_30_60_bucket(self): # invoice 2026-06-01, paid 2026-07-16 -> 45 days after -> 30-60 bucket invoice = create_sales_invoice(customer="_Test Customer 1", rate=1000, posting_date="2026-06-01") payment = self.pay_invoice(invoice, "2026-07-16") - columns, data = self.run_report() + _columns, data = self.run_report() row = self.find_payment_row(data, payment.name) self.assertIsNotNone(row, "Payment row not found in report output") - self.assertEqual(row[8], 1000) # amount - self.assertEqual(row[10], 45) # age = payment date - invoice date + self.assertEqual(row["amount"], 1000) + self.assertEqual(row["age"], 45) # Buckets: 30-60 filled, others empty. - self.assertEqual(row[11], 0) # range1 (0-30) - self.assertEqual(row[12], 1000) # range2 (30-60) - self.assertEqual(row[13], 0) # range3 (60-90) - self.assertEqual(row[14], 0) # range4 (90 Above) + self.assertEqual(row["range1"], 0) + self.assertEqual(row["range2"], 1000) + self.assertEqual(row["range3"], 0) + self.assertEqual(row["range4"], 0) + + def test_payment_over_90_days_lands_in_90_above_bucket(self): + # invoice 2026-01-01, paid 2026-06-01 -> 151 days after -> "90 Above" bucket. + # Regression guard: with four range columns, a payment older than the last + # threshold must fall into range4 rather than an unread range5 (showing 0). + invoice = create_sales_invoice(customer="_Test Customer 2", rate=1000, posting_date="2026-01-01") + payment = self.pay_invoice(invoice, "2026-06-01") + + _columns, data = self.run_report() + + row = self.find_payment_row(data, payment.name) + self.assertIsNotNone(row, "Payment row not found in report output") + + self.assertEqual(row["amount"], 1000) + self.assertEqual(row["age"], 151) + self.assertEqual(row["range1"], 0) + self.assertEqual(row["range2"], 0) + self.assertEqual(row["range3"], 0) + self.assertEqual(row["range4"], 1000) # 90 Above captures the full amount def test_columns_expose_expected_age_buckets(self): columns, _data = self.run_report()