mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-27 13:55:19 +00:00
fix: age payments by the payment period (payment date - invoice date)
This commit is contained in:
@@ -21,6 +21,8 @@ def execute(filters=None):
|
|||||||
entries = get_entries(filters)
|
entries = get_entries(filters)
|
||||||
invoice_details = get_invoice_posting_date_map(filters)
|
invoice_details = get_invoice_posting_date_map(filters)
|
||||||
|
|
||||||
|
report = ReceivablePayableReport(filters)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for d in entries:
|
for d in entries:
|
||||||
invoice = invoice_details.get(d.against_voucher_no) or frappe._dict()
|
invoice = invoice_details.get(d.against_voucher_no) or frappe._dict()
|
||||||
@@ -29,7 +31,9 @@ def execute(filters=None):
|
|||||||
d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
|
d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
|
||||||
|
|
||||||
if d.against_voucher_no:
|
if d.against_voucher_no:
|
||||||
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
|
# age the payment by how long after the invoice it was made (payment date - invoice date)
|
||||||
|
report.age_as_on = getdate(d.posting_date)
|
||||||
|
report.get_ageing_data(invoice.posting_date, d)
|
||||||
|
|
||||||
row = [
|
row = [
|
||||||
d.voucher_type,
|
d.voucher_type,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_days, getdate, nowdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
@@ -16,10 +16,9 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
|
|||||||
"""Depth tests for the Payment Period Based On Invoice Date report.
|
"""Depth tests for the Payment Period Based On Invoice Date report.
|
||||||
|
|
||||||
The report lists Payment Ledger Entries against invoices and buckets the paid
|
The report lists Payment Ledger Entries against invoices and buckets the paid
|
||||||
amount by the invoice's age into ranges: range1 (0-30), range2 (30-60),
|
amount by the payment period -- how long after the invoice the payment was made
|
||||||
range3 (60-90), range4 (90 Above). The age is measured from the invoice
|
(payment date - invoice date) -- into ranges: range1 (0-30), range2 (30-60),
|
||||||
posting date up to today, so invoice posting dates are placed relative to
|
range3 (60-90), range4 (90 Above).
|
||||||
``nowdate()`` to land deterministically in a given bucket.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def run_report(self, **extra):
|
def run_report(self, **extra):
|
||||||
@@ -28,8 +27,8 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
|
|||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"payment_type": "Incoming",
|
"payment_type": "Incoming",
|
||||||
"party_type": "Customer",
|
"party_type": "Customer",
|
||||||
"from_date": add_days(nowdate(), -180),
|
"from_date": "2026-01-01",
|
||||||
"to_date": nowdate(),
|
"to_date": "2026-12-31",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
filters.update(extra)
|
filters.update(extra)
|
||||||
@@ -45,21 +44,18 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
|
|||||||
return row
|
return row
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def pay_invoice(self, invoice):
|
def pay_invoice(self, invoice, payment_date):
|
||||||
pe = get_payment_entry("Sales Invoice", invoice.name)
|
pe = get_payment_entry("Sales Invoice", invoice.name)
|
||||||
pe.posting_date = nowdate()
|
pe.posting_date = payment_date
|
||||||
pe.reference_no = "1"
|
pe.reference_no = "1"
|
||||||
pe.reference_date = nowdate()
|
pe.reference_date = payment_date
|
||||||
pe.submit()
|
pe.submit()
|
||||||
return pe
|
return pe
|
||||||
|
|
||||||
def test_paid_amount_lands_in_0_30_bucket(self):
|
def test_paid_amount_lands_in_0_30_bucket(self):
|
||||||
invoice = create_sales_invoice(
|
# invoice 2026-06-01, paid 2026-06-20 -> 19 days after -> 0-30 bucket
|
||||||
customer="_Test Customer",
|
invoice = create_sales_invoice(customer="_Test Customer", rate=1000, posting_date="2026-06-01")
|
||||||
rate=1000,
|
payment = self.pay_invoice(invoice, "2026-06-20")
|
||||||
posting_date=add_days(nowdate(), -15),
|
|
||||||
)
|
|
||||||
payment = self.pay_invoice(invoice)
|
|
||||||
|
|
||||||
columns, data = self.run_report()
|
columns, data = self.run_report()
|
||||||
|
|
||||||
@@ -68,10 +64,11 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
|
|||||||
|
|
||||||
# Positional assertions on the row shape.
|
# Positional assertions on the row shape.
|
||||||
self.assertEqual(row[2], "Customer")
|
self.assertEqual(row[2], "Customer")
|
||||||
self.assertEqual(row[4], getdate(nowdate())) # payment posting date
|
self.assertEqual(row[4], getdate("2026-06-20")) # payment posting date
|
||||||
self.assertEqual(row[5], invoice.name) # against invoice
|
self.assertEqual(row[5], invoice.name) # against invoice
|
||||||
self.assertEqual(row[6], getdate(add_days(nowdate(), -15))) # invoice posting date
|
self.assertEqual(row[6], getdate("2026-06-01")) # invoice posting date
|
||||||
self.assertEqual(row[8], 1000) # amount
|
self.assertEqual(row[8], 1000) # amount
|
||||||
|
self.assertEqual(row[10], 19) # age = payment date - invoice date
|
||||||
|
|
||||||
# Buckets: 0-30 filled, others empty.
|
# Buckets: 0-30 filled, others empty.
|
||||||
self.assertEqual(row[11], 1000) # range1 (0-30)
|
self.assertEqual(row[11], 1000) # range1 (0-30)
|
||||||
@@ -80,12 +77,9 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
|
|||||||
self.assertEqual(row[14], 0) # range4 (90 Above)
|
self.assertEqual(row[14], 0) # range4 (90 Above)
|
||||||
|
|
||||||
def test_paid_amount_lands_in_30_60_bucket(self):
|
def test_paid_amount_lands_in_30_60_bucket(self):
|
||||||
invoice = create_sales_invoice(
|
# invoice 2026-06-01, paid 2026-07-16 -> 45 days after -> 30-60 bucket
|
||||||
customer="_Test Customer 1",
|
invoice = create_sales_invoice(customer="_Test Customer 1", rate=1000, posting_date="2026-06-01")
|
||||||
rate=1000,
|
payment = self.pay_invoice(invoice, "2026-07-16")
|
||||||
posting_date=add_days(nowdate(), -45),
|
|
||||||
)
|
|
||||||
payment = self.pay_invoice(invoice)
|
|
||||||
|
|
||||||
columns, data = self.run_report()
|
columns, data = self.run_report()
|
||||||
|
|
||||||
@@ -93,6 +87,7 @@ class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
|
|||||||
self.assertIsNotNone(row, "Payment row not found in report output")
|
self.assertIsNotNone(row, "Payment row not found in report output")
|
||||||
|
|
||||||
self.assertEqual(row[8], 1000) # amount
|
self.assertEqual(row[8], 1000) # amount
|
||||||
|
self.assertEqual(row[10], 45) # age = payment date - invoice date
|
||||||
# Buckets: 30-60 filled, others empty.
|
# Buckets: 30-60 filled, others empty.
|
||||||
self.assertEqual(row[11], 0) # range1 (0-30)
|
self.assertEqual(row[11], 0) # range1 (0-30)
|
||||||
self.assertEqual(row[12], 1000) # range2 (30-60)
|
self.assertEqual(row[12], 1000) # range2 (30-60)
|
||||||
|
|||||||
Reference in New Issue
Block a user