|
| 1 | +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors |
| 2 | +# See license.txt |
| 3 | + |
| 4 | +import frappe |
| 5 | +from frappe.utils import flt |
| 6 | + |
| 7 | +from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry |
| 8 | +from erpnext.accounts.report.custom_financial_statement.custom_financial_statement import execute |
| 9 | +from erpnext.tests.utils import ERPNextTestSuite |
| 10 | + |
| 11 | + |
| 12 | +class TestCustomFinancialStatement(ERPNextTestSuite): |
| 13 | + """The report renders a Financial Report Template through FinancialReportEngine. |
| 14 | + These tests exercise its own entry point: a template with an account-data row |
| 15 | + and a calculated row, and the guard that returns nothing without a template.""" |
| 16 | + |
| 17 | + def setUp(self): |
| 18 | + frappe.set_user("Administrator") |
| 19 | + self.company = "_Test Company" |
| 20 | + self.expense_account = "_Test Account Cost for Goods Sold - _TC" |
| 21 | + self.cash_account = "Cash - _TC" |
| 22 | + |
| 23 | + def _make_template(self): |
| 24 | + # rows filter by exact account name so the value is isolated from other data |
| 25 | + template_name = f"Test Custom FS {frappe.generate_hash()[:8]}" |
| 26 | + return frappe.get_doc( |
| 27 | + { |
| 28 | + "doctype": "Financial Report Template", |
| 29 | + "template_name": template_name, |
| 30 | + "report_type": "Profit and Loss Statement", |
| 31 | + "rows": [ |
| 32 | + { |
| 33 | + "reference_code": "EXP", |
| 34 | + "display_name": "Test Expense", |
| 35 | + "indentation_level": 0, |
| 36 | + "data_source": "Account Data", |
| 37 | + "balance_type": "Closing Balance", |
| 38 | + "calculation_formula": f'["name", "=", "{self.expense_account}"]', |
| 39 | + }, |
| 40 | + { |
| 41 | + "reference_code": "EXP_X2", |
| 42 | + "display_name": "Expense Doubled", |
| 43 | + "indentation_level": 0, |
| 44 | + "data_source": "Calculated Amount", |
| 45 | + "calculation_formula": "EXP * 2", |
| 46 | + }, |
| 47 | + ], |
| 48 | + } |
| 49 | + ).insert() |
| 50 | + |
| 51 | + def _filters(self, template_name): |
| 52 | + return frappe._dict( |
| 53 | + { |
| 54 | + "company": self.company, |
| 55 | + "report_template": template_name, |
| 56 | + "from_fiscal_year": "2024", |
| 57 | + "to_fiscal_year": "2024", |
| 58 | + "period_start_date": "2024-01-01", |
| 59 | + "period_end_date": "2024-12-31", |
| 60 | + "filter_based_on": "Date Range", |
| 61 | + "periodicity": "Yearly", |
| 62 | + "accumulated_values": 0, |
| 63 | + } |
| 64 | + ) |
| 65 | + |
| 66 | + def test_account_and_calculated_rows(self): |
| 67 | + make_journal_entry( |
| 68 | + self.expense_account, |
| 69 | + self.cash_account, |
| 70 | + 2000, |
| 71 | + posting_date="2024-06-15", |
| 72 | + company=self.company, |
| 73 | + submit=True, |
| 74 | + ) |
| 75 | + template = self._make_template() |
| 76 | + |
| 77 | + columns, data = execute(self._filters(template.template_name))[:2] |
| 78 | + self.assertTrue(columns) |
| 79 | + |
| 80 | + rows = {row.get("account_name"): row for row in data} |
| 81 | + self.assertIn("Test Expense", rows) |
| 82 | + self.assertIn("Expense Doubled", rows) |
| 83 | + |
| 84 | + period_keys = rows["Test Expense"].get("_segment_info", {}).get("period_keys", []) |
| 85 | + self.assertTrue(period_keys, "expected at least one period key in _segment_info") |
| 86 | + period_key = period_keys[0] |
| 87 | + |
| 88 | + # the account-data row picks up the posted expense; the calculated row doubles it |
| 89 | + self.assertEqual(flt(rows["Test Expense"][period_key]), 2000.0) |
| 90 | + self.assertEqual(flt(rows["Expense Doubled"][period_key]), 4000.0) |
| 91 | + |
| 92 | + def test_no_template_returns_nothing(self): |
| 93 | + """Without a report_template the report short-circuits and returns None.""" |
| 94 | + self.assertIsNone(execute(frappe._dict({"company": self.company}))) |
0 commit comments