From 636bcbedc0c782678a2fefbee5a457ecf3bf73b2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 22 Jun 2026 13:29:02 +0530 Subject: [PATCH] feat(balance-sheet): implement execute_synced_report with full parity to normal report Co-Authored-By: Claude Sonnet 4.6 (cherry picked from commit bb195408165aa6c69771e75dd36e3b80ca1f2f3a) --- .../report/balance_sheet/balance_sheet.json | 10 +- .../report/balance_sheet/balance_sheet.py | 204 +++++++++++++++++- 2 files changed, 212 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.json b/erpnext/accounts/report/balance_sheet/balance_sheet.json index 4c1d4b64030..a992e189d61 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.json +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.json @@ -4,13 +4,20 @@ "columns": [], "creation": "2014-07-14 05:24:20.385279", "default_print_format": "Balance Sheet Standard", + "disable_prepared_report_automation": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "doctype_to_sync": [ + { + "doc_type": "GL Entry" + } + ], "filters": [], + "generate_csv": 0, "idx": 3, "is_standard": "Yes", - "modified": "2026-05-22 14:35:28.187799", + "modified": "2026-06-22 13:06:12.602924", "modified_by": "Administrator", "module": "Accounts", "name": "Balance Sheet", @@ -30,5 +37,6 @@ "role": "Auditor" } ], + "synced_report": 1, "timeout": 0 } diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index a8531e58acb..756d0c2ebbb 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -4,18 +4,27 @@ import frappe from frappe import _ -from frappe.utils import cint, flt +from frappe.utils import add_days, cint, flt from erpnext.accounts.doctype.financial_report_template.financial_report_engine import ( FinancialReportEngine, get_xlsx_styles, #! DO NOT REMOVE - hook for styling ) from erpnext.accounts.report.financial_statements import ( + accumulate_values_into_parents, + add_total_row, + calculate_values, compute_growth_view_data, + filter_accounts, + filter_out_zero_value_rows, + get_accounting_entries, + get_accounts, + get_appropriate_currency, get_columns, get_data, get_filtered_list_for_consolidated_report, get_period_list, + prepare_data, ) @@ -266,3 +275,196 @@ def get_chart_data(filters, chart_columns, asset, liability, equity, currency): chart["currency"] = currency return chart + + +def execute_synced_report(filters): + from frappe.database.duckdb.database import get_latest_sync + + if not (conn := get_latest_sync("GL Entry")): + frappe.throw(_("Balance Sheet requires {0} to be synced to DuckDB").format(frappe.bold("GL Entry"))) + + period_list = get_period_list( + filters.from_fiscal_year, + filters.to_fiscal_year, + filters.period_start_date, + filters.period_end_date, + filters.filter_based_on, + filters.periodicity, + company=filters.company, + ) + filters.period_start_date = period_list[0]["year_start_date"] + + currency = filters.presentation_currency or frappe.get_cached_value( + "Company", filters.company, "default_currency" + ) + + asset = _get_data_duckdb(conn, filters, "Asset", "Debit", period_list) + liability = _get_data_duckdb(conn, filters, "Liability", "Credit", period_list) + equity = _get_data_duckdb(conn, filters, "Equity", "Credit", period_list) + + provisional_profit_loss, total_credit = get_provisional_profit_loss( + asset, liability, equity, period_list, filters.company, currency + ) + message, opening_balance = check_opening_balance(asset, liability, equity) + + data = [] + data.extend(asset or []) + data.extend(liability or []) + data.extend(equity or []) + if opening_balance and round(opening_balance, 2) != 0: + unclosed = { + "account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", + "account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", + "warn_if_negative": True, + "currency": currency, + } + for period in period_list: + unclosed[period.key] = opening_balance + if provisional_profit_loss: + provisional_profit_loss[period.key] = provisional_profit_loss[period.key] - opening_balance + unclosed["total"] = opening_balance + data.append(unclosed) + + if provisional_profit_loss: + data.append(provisional_profit_loss) + if total_credit: + data.append(total_credit) + + columns = get_columns( + filters.periodicity, period_list, filters.accumulated_values, company=filters.company + ) + chart = get_chart_data(filters, period_list, asset, liability, equity, currency) + report_summary, primitive_summary = get_report_summary( + period_list, asset, liability, equity, provisional_profit_loss, currency, filters + ) + + if filters.get("selected_view") == "Growth": + compute_growth_view_data(data, period_list) + + return columns, data, message, chart, report_summary, primitive_summary + + +def _get_data_duckdb(conn, filters, root_type, balance_must_be, period_list): + accounts = get_accounts(filters.company, root_type) + if not accounts: + return None + + accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) + company_currency = get_appropriate_currency(filters.company, filters) + + gl_entries_by_account = {} + _load_gl_entries_duckdb(conn, filters, period_list, accounts, gl_entries_by_account, root_type) + + calculate_values( + accounts_by_name, + gl_entries_by_account, + period_list, + filters.accumulated_values, + False, + ) + accumulate_values_into_parents(accounts, accounts_by_name, period_list) + + out = prepare_data( + accounts, + balance_must_be, + period_list, + company_currency, + accumulated_values=filters.accumulated_values, + ) + out = filter_out_zero_value_rows(out, parent_children_map, filters.show_zero_values) + + if out: + add_total_row(out, root_type, balance_must_be, period_list, company_currency) + + return out + + +def _load_gl_entries_duckdb(conn, filters, period_list, accounts, gl_entries_by_account, root_type): + from erpnext.accounts.report.trial_balance.trial_balance import ( + _extra_gl_conditions, + _fetch_gl_rows_duckdb, + ) + from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency + + company = filters.company + year_start_date = period_list[0]["year_start_date"] + last_to_date = period_list[-1]["to_date"] + ignore_is_opening = frappe.get_single_value("Accounts Settings", "ignore_is_opening_check_for_reporting") + + leaf_accounts = [acc.name for acc in accounts if not acc.is_group] + if not leaf_accounts: + return + + opening_from_date = None + ignore_opening_entries = False + + ignore_closing_balances = frappe.get_single_value("Accounts Settings", "ignore_account_closing_balance") + if not ignore_closing_balances: + last_pcv_list = frappe.db.get_all( + "Period Closing Voucher", + filters={ + "docstatus": 1, + "company": company, + "period_end_date": ("<", filters.get("period_start_date") or year_start_date), + }, + fields=["period_end_date", "name"], + order_by="period_end_date desc", + limit=1, + ) + if last_pcv_list: + last_pcv = last_pcv_list[0] + pcv_entries = get_accounting_entries( + "Account Closing Balance", + None, + last_to_date, + filters, + root_type=root_type, + ignore_closing_entries=False, + period_closing_voucher=last_pcv.name, + ) + if filters.get("presentation_currency"): + convert_to_presentation_currency(pcv_entries, get_currency(filters)) + for entry in pcv_entries: + gl_entries_by_account.setdefault(entry.account, []).append(entry) + opening_from_date = add_days(last_pcv.period_end_date, 1) + ignore_opening_entries = True + + extra_cond, extra_params = _extra_gl_conditions(filters) + account_placeholders = ", ".join(["?"] * len(leaf_accounts)) + base_conds = [ + "company = ?", + "is_cancelled = 0", + f"account IN ({account_placeholders})", + ] + base_params = [company, *leaf_accounts] + if ignore_opening_entries and not ignore_is_opening: + base_conds.append("is_opening = 'No'") + base_conds.extend(extra_cond) + base_params.extend(extra_params) + + # Opening GL entries from DuckDB (entries before year_start_date) + open_conds = [*base_conds, "posting_date < ?"] + open_params = [*base_params, year_start_date] + if opening_from_date: + open_conds = [*open_conds, "posting_date >= ?"] + open_params = [*open_params, opening_from_date] + + opening_entries = _fetch_gl_rows_duckdb(conn, open_conds, open_params) + if filters.get("presentation_currency"): + convert_to_presentation_currency(opening_entries, get_currency(filters)) + synthetic_open_date = add_days(year_start_date, -1) + for entry in opening_entries: + entry.posting_date = synthetic_open_date + gl_entries_by_account.setdefault(entry.account, []).append(entry) + + # Period GL entries from DuckDB (one aggregated query per period) + for period in period_list: + period_conds = [*base_conds, "posting_date >= ?", "posting_date <= ?"] + period_params = [*base_params, period.from_date, period.to_date] + + period_entries = _fetch_gl_rows_duckdb(conn, period_conds, period_params) + if filters.get("presentation_currency"): + convert_to_presentation_currency(period_entries, get_currency(filters)) + for entry in period_entries: + entry.posting_date = period.to_date + gl_entries_by_account.setdefault(entry.account, []).append(entry)