diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_engine.py b/erpnext/accounts/doctype/financial_report_template/financial_report_engine.py index af45c1f3f8e..47c7e2e6366 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_engine.py +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_engine.py @@ -565,18 +565,19 @@ class FinancialQueryBuilder: frappe.qb.from_(acb_table) .select( acb_table.account, - (acb_table.debit - acb_table.credit).as_("balance"), + Sum(acb_table.debit - acb_table.credit).as_("balance"), ) .where(acb_table.company == self.company) .where(acb_table.account.isin(account_names)) .where(acb_table.period_closing_voucher == closing_voucher) + .groupby(acb_table.account) ) query = self._apply_standard_filters(query, acb_table, "Account Closing Balance") results = self._execute_with_permissions(query, "Account Closing Balance") for row in results: - closing_balances[row["account"]] = row["balance"] + closing_balances[row["account"]] = row["balance"] or 0.0 return closing_balances diff --git a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py index a90a5d32aad..01dcff7f57f 100644 --- a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py +++ b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py @@ -19,6 +19,7 @@ from erpnext.accounts.doctype.financial_report_template.test_financial_report_te ) from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.utils import get_currency_precision, get_fiscal_year +from erpnext.tests.utils import change_settings class TestDependencyResolver(FinancialReportTemplateTestCase): @@ -1953,6 +1954,104 @@ class TestFinancialQueryBuilder(FinancialReportTemplateTestCase): jv_2023.cancel() + @change_settings("Accounts Settings", {"use_legacy_controller_for_pcv": 1}) + def test_opening_balance_sums_acb_rows_across_dimensions(self): + """ + Account Closing Balance stores one row per (account, cost_center, + project, finance_book). The closing-balance fetch must sum all rows. + """ + company = "_Test Company" + cash_account = "_Test Cash - _TC" + sales_account = "Sales - _TC" + cc_1 = "_Test Cost Center - _TC" + cc_2 = "_Test Cost Center 2 - _TC" + docs = [] + + try: + jv_2023_cc1 = make_journal_entry( + account1=cash_account, + account2=sales_account, + amount=3000, + posting_date="2023-06-15", + cost_center=cc_1, + company=company, + submit=True, + ) + docs.append(jv_2023_cc1) + jv_2023_cc2 = make_journal_entry( + account1=cash_account, + account2=sales_account, + amount=2000, + posting_date="2023-06-15", + cost_center=cc_2, + company=company, + submit=True, + ) + docs.append(jv_2023_cc2) + + fy_2023 = get_fiscal_year("2023-06-15", company=company) + + pcv = frappe.get_doc( + { + "doctype": "Period Closing Voucher", + "transaction_date": "2023-12-31", + "period_start_date": fy_2023[1], + "period_end_date": fy_2023[2], + "company": company, + "fiscal_year": fy_2023[0], + "cost_center": cc_1, + "closing_account_head": "Deferred Revenue - _TC", + "remarks": "Test multi-dim PCV", + } + ) + pcv.insert() + pcv.submit() + docs.append(pcv) + + jv_2024 = make_journal_entry( + account1=cash_account, + account2=sales_account, + amount=100, + posting_date="2024-01-15", + cost_center=cc_1, + company=company, + submit=True, + ) + docs.append(jv_2024) + + filters = { + "company": company, + "from_fiscal_year": "2024", + "to_fiscal_year": "2024", + "period_start_date": "2024-01-01", + "period_end_date": "2024-03-31", + "filter_based_on": "Date Range", + "periodicity": "Monthly", + "ignore_closing_entries": True, + } + periods = [ + {"key": "2024_jan", "from_date": "2024-01-01", "to_date": "2024-01-31"}, + {"key": "2024_feb", "from_date": "2024-02-01", "to_date": "2024-02-29"}, + {"key": "2024_mar", "from_date": "2024-03-01", "to_date": "2024-03-31"}, + ] + + query_builder = FinancialQueryBuilder(filters, periods) + accounts = [ + frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}), + ] + + balances_data = query_builder.fetch_account_balances(accounts) + cash_data = balances_data.get(cash_account) + self.assertIsNotNone(cash_data, "Cash account must appear in results") + + jan_cash = cash_data.get_period("2024_jan") + self.assertEqual(jan_cash.opening, 5000.0) + self.assertEqual(jan_cash.movement, 100.0) + self.assertEqual(jan_cash.closing, 5100.0) + + finally: + self.cancel_docs(docs) + def test_opening_entries_roll_into_opening_after_period_closing(self): """ Sequence: diff --git a/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py b/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py index 9372c503d12..e3ca33a747e 100644 --- a/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py +++ b/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py @@ -9,6 +9,14 @@ from erpnext.tests.utils import ERPNextTestSuite class FinancialReportTemplateTestCase(ERPNextTestSuite): """Utility class with common setup and helper methods for all test classes""" + def cancel_docs(self, docs): + """Cancel submitted docs in reverse creation order to avoid dependency issues.""" + for doc in reversed(docs): + if doc: + doc.reload() + if doc.docstatus == 1: + doc.cancel() + def setUp(self): """Set up test data""" self.create_test_template()