From 68330843d8d04eb29d629973cef976f7ea88a9e6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 10:32:43 +0530 Subject: [PATCH 1/3] test: add coverage for Purchase Receipt Trends report Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_purchase_receipt_trends.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py diff --git a/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py new file mode 100644 index 00000000000..87511222754 --- /dev/null +++ b/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py @@ -0,0 +1,40 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe + +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.stock.report.purchase_receipt_trends.purchase_receipt_trends import execute +from erpnext.tests.utils import ERPNextTestSuite + + +class TestPurchaseReceiptTrends(ERPNextTestSuite): + def run_report(self, **extra): + filters = frappe._dict( + { + "company": "_Test Company", + "fiscal_year": "_Test Fiscal Year 2026", + "period": "Yearly", + "based_on": "Item", + "group_by": "", + } + ) + filters.update(extra) + return execute(filters)[1] + + def test_receipt_qty_in_trend(self): + item = make_item(properties={"is_stock_item": 1, "is_purchase_item": 1}).name + make_purchase_receipt( + item_code=item, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" + ) + + data = self.run_report() + + # Row layout for based_on="Item", Yearly, no group_by: + # [item_code, item_name, currency, FY(Qty), FY(Amt), Total(Qty), Total(Amt)] + row = next(r for r in data if r[0] == item) + self.assertEqual(row[3], 10) # fiscal-year qty + self.assertEqual(row[4], 1000) # fiscal-year amount (10 * 100) + self.assertEqual(row[5], 10) # Total(Qty) + self.assertEqual(row[6], 1000) # Total(Amt) From 04fd425fb65a772038f46fc2f047564331b55627 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 13:27:43 +0530 Subject: [PATCH 2/3] test: reuse BootStrapTestData master data to reduce runtime Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_purchase_receipt_trends.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py index 87511222754..bd11379ec29 100644 --- a/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py @@ -3,7 +3,6 @@ import frappe -from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.report.purchase_receipt_trends.purchase_receipt_trends import execute from erpnext.tests.utils import ERPNextTestSuite @@ -23,18 +22,25 @@ class TestPurchaseReceiptTrends(ERPNextTestSuite): filters.update(extra) return execute(filters)[1] + def get_item_totals(self, data, item): + # Row layout for based_on="Item", Yearly, no group_by: + # [item_code, item_name, currency, FY(Qty), FY(Amt), Total(Qty), Total(Amt)] + row = next((r for r in data if r[0] == item), None) + if row is None: + return 0, 0 + return row[3], row[4] + def test_receipt_qty_in_trend(self): - item = make_item(properties={"is_stock_item": 1, "is_purchase_item": 1}).name + item = "_Test Item" + + # The report sums ALL purchase receipts for the item in the fiscal year, so capture + # any pre-existing committed baseline and assert only this receipt's contribution. + base_qty, base_amt = self.get_item_totals(self.run_report(), item) + make_purchase_receipt( item_code=item, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" ) - data = self.run_report() - - # Row layout for based_on="Item", Yearly, no group_by: - # [item_code, item_name, currency, FY(Qty), FY(Amt), Total(Qty), Total(Amt)] - row = next(r for r in data if r[0] == item) - self.assertEqual(row[3], 10) # fiscal-year qty - self.assertEqual(row[4], 1000) # fiscal-year amount (10 * 100) - self.assertEqual(row[5], 10) # Total(Qty) - self.assertEqual(row[6], 1000) # Total(Amt) + qty, amt = self.get_item_totals(self.run_report(), item) + self.assertEqual(qty - base_qty, 10) # fiscal-year qty + self.assertEqual(amt - base_amt, 1000) # fiscal-year amount (10 * 100) From d2abb569d4adc13088b15834cc21dd7e5e197e5e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 14:49:27 +0530 Subject: [PATCH 3/3] test: cover period, based_on and group_by filters in Purchase Receipt Trends --- .../test_purchase_receipt_trends.py | 120 +++++++++++++++--- 1 file changed, 103 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py index bd11379ec29..220f80616ed 100644 --- a/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/test_purchase_receipt_trends.py @@ -7,9 +7,16 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.report.purchase_receipt_trends.purchase_receipt_trends import execute from erpnext.tests.utils import ERPNextTestSuite +ITEM = "_Test Item" +ITEM_GROUP = "_Test Item Group" +SUPPLIER = "_Test Supplier" + class TestPurchaseReceiptTrends(ERPNextTestSuite): def run_report(self, **extra): + return self.run_report_full(**extra)[1] + + def run_report_full(self, **extra): filters = frappe._dict( { "company": "_Test Company", @@ -20,27 +27,106 @@ class TestPurchaseReceiptTrends(ERPNextTestSuite): } ) filters.update(extra) - return execute(filters)[1] + columns, data = execute(filters)[:2] + return columns, data - def get_item_totals(self, data, item): - # Row layout for based_on="Item", Yearly, no group_by: - # [item_code, item_name, currency, FY(Qty), FY(Amt), Total(Qty), Total(Amt)] - row = next((r for r in data if r[0] == item), None) - if row is None: - return 0, 0 - return row[3], row[4] + # trend columns are "Label:fieldtype:width" strings; assert by label so the index + # stays correct across period / based_on / group_by combinations. + @staticmethod + def labels(columns): + return [c.split(":")[0] if isinstance(c, str) else c.get("label") for c in columns] + + def find_row(self, columns, data, match): + labels = self.labels(columns) + for row in data: + if all(row[labels.index(label)] == value for label, value in match.items()): + return row + return None + + def value(self, columns, row, label): + if not row: + return 0 + return row[self.labels(columns).index(label)] or 0 + + def values(self, match, wanted_labels, **extra): + columns, data = self.run_report_full(**extra) + row = self.find_row(columns, data, match) + return {label: self.value(columns, row, label) for label in wanted_labels} def test_receipt_qty_in_trend(self): - item = "_Test Item" - # The report sums ALL purchase receipts for the item in the fiscal year, so capture - # any pre-existing committed baseline and assert only this receipt's contribution. - base_qty, base_amt = self.get_item_totals(self.run_report(), item) - + # any pre-existing baseline and assert only this receipt's contribution. + cols = ["_Test Fiscal Year 2026 (Qty)", "_Test Fiscal Year 2026 (Amt)"] + before = self.values({"Item": ITEM}, cols) make_purchase_receipt( - item_code=item, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" + item_code=ITEM, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" ) + after = self.values({"Item": ITEM}, cols) + self.assertEqual(after[cols[0]] - before[cols[0]], 10) + self.assertEqual(after[cols[1]] - before[cols[1]], 1000) - qty, amt = self.get_item_totals(self.run_report(), item) - self.assertEqual(qty - base_qty, 10) # fiscal-year qty - self.assertEqual(amt - base_amt, 1000) # fiscal-year amount (10 * 100) + def test_monthly_period_buckets(self): + cols = ["Jun (Qty)", "Jun (Amt)", "Total(Qty)", "Total(Amt)"] + before = self.values({"Item": ITEM}, cols, period="Monthly") + make_purchase_receipt( + item_code=ITEM, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" + ) + after = self.values({"Item": ITEM}, cols, period="Monthly") + # the June receipt lands only in the June bucket, and rolls up into the Total columns + self.assertEqual(after["Jun (Qty)"] - before["Jun (Qty)"], 10) + self.assertEqual(after["Jun (Amt)"] - before["Jun (Amt)"], 1000) + self.assertEqual(after["Total(Qty)"] - before["Total(Qty)"], 10) + self.assertEqual(after["Total(Amt)"] - before["Total(Amt)"], 1000) + + def test_quarterly_period_buckets(self): + # 2026-06-01 falls in the Apr-Jun quarter + cols = ["Apr-Jun (Qty)", "Apr-Jun (Amt)", "Total(Qty)"] + before = self.values({"Item": ITEM}, cols, period="Quarterly") + make_purchase_receipt( + item_code=ITEM, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" + ) + after = self.values({"Item": ITEM}, cols, period="Quarterly") + self.assertEqual(after["Apr-Jun (Qty)"] - before["Apr-Jun (Qty)"], 10) + self.assertEqual(after["Apr-Jun (Amt)"] - before["Apr-Jun (Amt)"], 1000) + self.assertEqual(after["Total(Qty)"] - before["Total(Qty)"], 10) + + def test_based_on_supplier(self): + cols = ["Total(Qty)", "Total(Amt)"] + before = self.values({"Supplier": SUPPLIER}, cols, based_on="Supplier") + make_purchase_receipt( + item_code=ITEM, + qty=10, + rate=100, + supplier=SUPPLIER, + company="_Test Company", + posting_date="2026-06-01", + ) + after = self.values({"Supplier": SUPPLIER}, cols, based_on="Supplier") + self.assertEqual(after["Total(Qty)"] - before["Total(Qty)"], 10) + self.assertEqual(after["Total(Amt)"] - before["Total(Amt)"], 1000) + + def test_based_on_item_group(self): + cols = ["Total(Qty)", "Total(Amt)"] + before = self.values({"Item Group": ITEM_GROUP}, cols, based_on="Item Group") + make_purchase_receipt( + item_code=ITEM, qty=10, rate=100, company="_Test Company", posting_date="2026-06-01" + ) + after = self.values({"Item Group": ITEM_GROUP}, cols, based_on="Item Group") + self.assertEqual(after["Total(Qty)"] - before["Total(Qty)"], 10) + self.assertEqual(after["Total(Amt)"] - before["Total(Amt)"], 1000) + + def test_group_by_item_under_supplier(self): + # based_on=Supplier with group_by=Item produces an item-wise breakdown row + cols = ["Total(Qty)", "Total(Amt)"] + before = self.values({"Item": ITEM}, cols, based_on="Supplier", group_by="Item") + make_purchase_receipt( + item_code=ITEM, + qty=10, + rate=100, + supplier=SUPPLIER, + company="_Test Company", + posting_date="2026-06-01", + ) + after = self.values({"Item": ITEM}, cols, based_on="Supplier", group_by="Item") + self.assertEqual(after["Total(Qty)"] - before["Total(Qty)"], 10) + self.assertEqual(after["Total(Amt)"] - before["Total(Amt)"], 1000)