mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 23:18:40 +00:00
feat(balance-sheet): implement execute_synced_report with full parity to normal report
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit bb19540816)
This commit is contained in:
@@ -4,13 +4,20 @@
|
|||||||
"columns": [],
|
"columns": [],
|
||||||
"creation": "2014-07-14 05:24:20.385279",
|
"creation": "2014-07-14 05:24:20.385279",
|
||||||
"default_print_format": "Balance Sheet Standard",
|
"default_print_format": "Balance Sheet Standard",
|
||||||
|
"disable_prepared_report_automation": 0,
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"doctype_to_sync": [
|
||||||
|
{
|
||||||
|
"doc_type": "GL Entry"
|
||||||
|
}
|
||||||
|
],
|
||||||
"filters": [],
|
"filters": [],
|
||||||
|
"generate_csv": 0,
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2026-05-22 14:35:28.187799",
|
"modified": "2026-06-22 13:06:12.602924",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Balance Sheet",
|
"name": "Balance Sheet",
|
||||||
@@ -30,5 +37,6 @@
|
|||||||
"role": "Auditor"
|
"role": "Auditor"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"synced_report": 1,
|
||||||
"timeout": 0
|
"timeout": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,27 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
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 (
|
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
|
||||||
FinancialReportEngine,
|
FinancialReportEngine,
|
||||||
get_xlsx_styles, #! DO NOT REMOVE - hook for styling
|
get_xlsx_styles, #! DO NOT REMOVE - hook for styling
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.financial_statements import (
|
from erpnext.accounts.report.financial_statements import (
|
||||||
|
accumulate_values_into_parents,
|
||||||
|
add_total_row,
|
||||||
|
calculate_values,
|
||||||
compute_growth_view_data,
|
compute_growth_view_data,
|
||||||
|
filter_accounts,
|
||||||
|
filter_out_zero_value_rows,
|
||||||
|
get_accounting_entries,
|
||||||
|
get_accounts,
|
||||||
|
get_appropriate_currency,
|
||||||
get_columns,
|
get_columns,
|
||||||
get_data,
|
get_data,
|
||||||
get_filtered_list_for_consolidated_report,
|
get_filtered_list_for_consolidated_report,
|
||||||
get_period_list,
|
get_period_list,
|
||||||
|
prepare_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -266,3 +275,196 @@ def get_chart_data(filters, chart_columns, asset, liability, equity, currency):
|
|||||||
chart["currency"] = currency
|
chart["currency"] = currency
|
||||||
|
|
||||||
return chart
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user