From 55c6d16d6969990df9cacae3c2f1e9c5ac363680 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 09:49:40 +0530 Subject: [PATCH 1/2] test: add coverage for Profitability Analysis report Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_profitability_analysis.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py diff --git a/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py new file mode 100644 index 00000000000..453587dbf9f --- /dev/null +++ b/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py @@ -0,0 +1,102 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe + +from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center +from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.report.profitability_analysis.profitability_analysis import execute +from erpnext.tests.utils import ERPNextTestSuite + +INCOME = "Sales - _TC" +EXPENSE = "_Test Account Cost for Goods Sold - _TC" +BANK = "_Test Bank - _TC" + + +class TestProfitabilityAnalysis(ERPNextTestSuite): + def run_report(self, fiscal_year="_Test Fiscal Year 2026", **extra): + filters = frappe._dict( + { + "company": "_Test Company", + "based_on": "Cost Center", + "fiscal_year": fiscal_year, + "from_date": "2026-01-01", + "to_date": "2026-12-31", + **extra, + } + ) + return execute(filters)[1] + + def make_cc(self, name, **args): + create_cost_center(cost_center_name=name, **args) + return name + " - _TC" + + def row(self, data, account): + return next(r for r in data if r.get("account") == account) + + def book_income(self, cost_center, amount, posting_date="2026-06-01"): + create_sales_invoice( + cost_center=cost_center, income_account=INCOME, rate=amount, qty=1, posting_date=posting_date + ) + + def book_expense(self, cost_center, amount, posting_date="2026-06-01"): + make_journal_entry(EXPENSE, BANK, amount, cost_center=cost_center, posting_date=posting_date, submit=True) + + def test_income_expense_and_gross_profit(self): + cc = self.make_cc("_Test PA Leaf") + self.book_income(cc, 10000) + self.book_expense(cc, 4000) + + row = self.row(self.run_report(), cc) + self.assertEqual(row["income"], 10000) + self.assertEqual(row["expense"], 4000) + self.assertEqual(row["gross_profit_loss"], 6000) + + def test_parent_cost_center_accumulates_children(self): + parent = self.make_cc("_Test PA Parent", is_group=1) + child_1 = self.make_cc("_Test PA Child 1", parent_cost_center=parent) + child_2 = self.make_cc("_Test PA Child 2", parent_cost_center=parent) + + self.book_income(child_1, 10000) + self.book_expense(child_2, 3000) + + data = self.run_report() + self.assertEqual(self.row(data, child_1)["income"], 10000) + self.assertEqual(self.row(data, child_2)["expense"], 3000) + + parent_row = self.row(data, parent) + self.assertEqual(parent_row["income"], 10000) + self.assertEqual(parent_row["expense"], 3000) + self.assertEqual(parent_row["gross_profit_loss"], 7000) + + def test_date_range_excludes_out_of_period_entries(self): + cc = self.make_cc("_Test PA Date") + self.book_income(cc, 10000, posting_date="2025-06-01") + + # the 2025 income must not appear in a 2026 report (zero-value rows are dropped) + accounts_2026 = {r.get("account") for r in self.run_report()} + self.assertNotIn(cc, accounts_2026) + + row_2025 = self.row( + self.run_report( + fiscal_year="_Test Fiscal Year 2025", from_date="2025-01-01", to_date="2025-12-31" + ), + cc, + ) + self.assertEqual(row_2025["income"], 10000) + + def test_total_row_sums_income_and_expense(self): + cc = self.make_cc("_Test PA Total") + self.book_income(cc, 10000) + self.book_expense(cc, 4000) + + data = self.run_report() + # the report appends a blank separator row and a totals row at the end + total_row = data[-1] + self.assertEqual(total_row["account"], "'Total'") + # total is built from direct (non-accumulated) values, so it stays internally consistent + self.assertEqual(total_row["gross_profit_loss"], total_row["income"] - total_row["expense"]) + # and it includes this test's bookings + self.assertGreaterEqual(total_row["income"], 10000) + self.assertGreaterEqual(total_row["expense"], 4000) From 03ecd2fd3a21ef8f5d7085861967ce21b1cb59a2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 13:36:39 +0530 Subject: [PATCH 2/2] test: reuse BootStrapTestData master data to reduce runtime Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_profitability_analysis.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py index 453587dbf9f..19e0c57ceb2 100644 --- a/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/test_profitability_analysis.py @@ -41,10 +41,13 @@ class TestProfitabilityAnalysis(ERPNextTestSuite): ) def book_expense(self, cost_center, amount, posting_date="2026-06-01"): - make_journal_entry(EXPENSE, BANK, amount, cost_center=cost_center, posting_date=posting_date, submit=True) + make_journal_entry( + EXPENSE, BANK, amount, cost_center=cost_center, posting_date=posting_date, submit=True + ) def test_income_expense_and_gross_profit(self): - cc = self.make_cc("_Test PA Leaf") + # bootstrap leaf cost center; clean of committed GL so exact assertions hold + cc = "_Test Cost Center - _TC" self.book_income(cc, 10000) self.book_expense(cc, 4000) @@ -71,7 +74,7 @@ class TestProfitabilityAnalysis(ERPNextTestSuite): self.assertEqual(parent_row["gross_profit_loss"], 7000) def test_date_range_excludes_out_of_period_entries(self): - cc = self.make_cc("_Test PA Date") + cc = "_Test Cost Center 2 - _TC" self.book_income(cc, 10000, posting_date="2025-06-01") # the 2025 income must not appear in a 2026 report (zero-value rows are dropped) @@ -87,7 +90,7 @@ class TestProfitabilityAnalysis(ERPNextTestSuite): self.assertEqual(row_2025["income"], 10000) def test_total_row_sums_income_and_expense(self): - cc = self.make_cc("_Test PA Total") + cc = "_Test Cost Center - _TC" self.book_income(cc, 10000) self.book_expense(cc, 4000)