From 6849d292f8b87de6052106ae52337927cda7f4af Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 17:35:52 +0530 Subject: [PATCH] refactor(postgres): port financial statements helper queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../accounts/report/financial_statements.py | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 478c603dbf5..320637721d7 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -179,11 +179,10 @@ def get_data( company_currency = get_appropriate_currency(company, filters) gl_entries_by_account = {} - for root in frappe.db.sql( - """select lft, rgt from tabAccount - where root_type=%s and ifnull(parent_account, '') = ''""", - root_type, - as_dict=1, + for root in frappe.get_all( + "Account", + filters={"root_type": root_type, "parent_account": ["is", "not set"]}, + fields=["lft", "rgt"], ): set_gl_entries_by_account( company, @@ -373,13 +372,23 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency def get_accounts(company, root_type): - return frappe.db.sql( - """ - select name, account_number, parent_account, lft, rgt, root_type, report_type, account_name, include_in_gross, account_type, is_group, lft, rgt - from `tabAccount` - where company=%s and root_type=%s order by lft""", - (company, root_type), - as_dict=True, + return frappe.get_all( + "Account", + filters={"company": company, "root_type": root_type}, + fields=[ + "name", + "account_number", + "parent_account", + "lft", + "rgt", + "root_type", + "report_type", + "account_name", + "include_in_gross", + "account_type", + "is_group", + ], + order_by="lft", ) @@ -529,7 +538,11 @@ def get_accounting_entries( gl_entry.credit_in_account_currency if not group_by_account else Sum(gl_entry.credit_in_account_currency).as_("credit_in_account_currency"), - gl_entry.account_currency, + # when grouping by account the non-aggregated columns must be aggregated for postgres; + # account_currency is constant per account so Max() returns the same value. + gl_entry.account_currency + if not group_by_account + else Max(gl_entry.account_currency).as_("account_currency"), ) .where(gl_entry.company == filters.company) ) @@ -547,15 +560,29 @@ def get_accounting_entries( ignore_is_opening = frappe.get_single_value("Accounts Settings", "ignore_is_opening_check_for_reporting") if doctype == "GL Entry": - query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year) + # aggregate the non-grouped columns when grouping by account (postgres requirement) + if group_by_account: + query = query.select( + Max(gl_entry.posting_date).as_("posting_date"), + Max(gl_entry.is_opening).as_("is_opening"), + Max(gl_entry.fiscal_year).as_("fiscal_year"), + ) + else: + query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year) query = query.where(gl_entry.is_cancelled == 0) query = query.where(gl_entry.posting_date <= to_date) - query = query.force_index("posting_date_company_index") + # FORCE INDEX is MySQL-only; postgres has no index hints (its planner uses the index anyway) + if frappe.db.db_type != "postgres": + query = query.force_index("posting_date_company_index") if ignore_opening_entries and not ignore_is_opening: query = query.where(gl_entry.is_opening == "No") else: - query = query.select(gl_entry.closing_date.as_("posting_date")) + query = query.select( + Max(gl_entry.closing_date).as_("posting_date") + if group_by_account + else gl_entry.closing_date.as_("posting_date") + ) query = query.where(gl_entry.period_closing_voucher == period_closing_voucher) query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters)