From 2e844a58fb36932b8d7e6da94bb136c37756f131 Mon Sep 17 00:00:00 2001 From: Shllokkk <140623894+Shllokkk@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:09:03 +0530 Subject: [PATCH] perf: optimize account balance data fetching for Chart Of Accounts --- .../accounts/doctype/account/account_tree.js | 97 +++++++++---------- erpnext/accounts/utils.py | 72 ++++++++++++++ 2 files changed, 118 insertions(+), 51 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 315b41560ce..5ff4e4a47e2 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -52,60 +52,55 @@ frappe.treeview_settings["Account"] = { ], root_label: "Accounts", get_tree_nodes: "erpnext.accounts.utils.get_children", - on_get_node: function (nodes, deep = false) { - if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + on_node_render: function (node, deep) { + const render_balances = () => { + for (let account of cur_tree.account_balance_data) { + const node = cur_tree.nodes && cur_tree.nodes[account.value]; + if (!node || node.is_root) continue; - let accounts = []; - if (deep) { - // in case of `get_all_nodes` - accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []); - } else { - accounts = nodes; - } + // show Dr if positive since balance is calculated as debit - credit else show Cr + const balance = account.balance_in_account_currency || account.balance; + const dr_or_cr = balance > 0 ? __("Dr") : __("Cr"); + const format = (value, currency) => format_currency(Math.abs(value), currency); - frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { - if (value) { - const get_balances = frappe.call({ - method: "erpnext.accounts.utils.get_account_balances", - args: { - accounts: accounts, - company: cur_tree.args.company, - include_default_fb_balances: true, - }, - }); - - get_balances.then((r) => { - if (!r.message || r.message.length == 0) return; - - for (let account of r.message) { - const node = cur_tree.nodes && cur_tree.nodes[account.value]; - if (!node || node.is_root) continue; - - // show Dr if positive since balance is calculated as debit - credit else show Cr - const balance = account.balance_in_account_currency || account.balance; - const dr_or_cr = balance > 0 ? __("Dr") : __("Cr"); - const format = (value, currency) => format_currency(Math.abs(value), currency); - - if (account.balance !== undefined) { - node.parent && node.parent.find(".balance-area").remove(); - $( - '' + - (account.balance_in_account_currency - ? format( - account.balance_in_account_currency, - account.account_currency - ) + " / " - : "") + - format(account.balance, account.company_currency) + - " " + - dr_or_cr + - "" - ).insertBefore(node.$ul); - } - } - }); + if (account.balance !== undefined) { + node.parent && node.parent.find(".balance-area").remove(); + $( + '' + + (account.account_currency != account.company_currency + ? format(account.balance_in_account_currency, account.account_currency) + + " / " + : "") + + format(account.balance, account.company_currency) + + " " + + dr_or_cr + + "" + ).insertBefore(node.$ul); + } } - }); + }; + + if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + if (!cur_tree.account_balance_data) { + frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { + if (value) { + frappe.call({ + method: "erpnext.accounts.utils.get_account_balances_coa", + args: { + company: cur_tree.args.company, + include_default_fb_balances: true, + }, + callback: function (r) { + if (!r.message || r.message.length === 0) return; + cur_tree.account_balance_data = r.message || []; + render_balances(); + }, + }); + } + }); + } else { + render_balances(); + } }, add_tree_node: "erpnext.accounts.utils.add_ac", menu_items: [ diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6b71228841f..c51fa2f0ffb 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1414,6 +1414,78 @@ def get_account_balances( return accounts +@frappe.whitelist() +def get_account_balances_coa(company: str, include_default_fb_balances: bool = False): + company_currency = frappe.get_cached_value("Company", company, "default_currency") + + Account = DocType("Account") + account_list = ( + frappe.qb.from_(Account) + .select(Account.name, Account.parent_account, Account.account_currency) + .where(Account.company == company) + .orderby(Account.lft) + .run(as_dict=True) + ) + + account_balances_cc = {account.get("name"): 0 for account in account_list} + + account_balances_ac = {account.get("name"): 0 for account in account_list} + + GLEntry = DocType("GL Entry") + precision = get_currency_precision() + get_ledger_balances_query = ( + frappe.qb.from_(GLEntry) + .select( + GLEntry.account, + (Sum(Round(GLEntry.debit, precision)) - Sum(Round(GLEntry.credit, precision))).as_("balance"), + ( + Sum(Round(GLEntry.debit_in_account_currency, precision)) + - Sum(Round(GLEntry.credit_in_account_currency, precision)) + ).as_("balance_in_account_currency"), + ) + .groupby(GLEntry.account) + ) + + condition_list = [GLEntry.company == company, GLEntry.is_cancelled == 0] + + default_finance_book = None + + if include_default_fb_balances: + default_finance_book = frappe.get_cached_value("Company", company, "default_finance_book") + + if default_finance_book: + condition_list.append( + (GLEntry.finance_book == default_finance_book) | (GLEntry.finance_book.isnull()) + ) + + for condition in condition_list: + get_ledger_balances_query = get_ledger_balances_query.where(condition) + + ledger_balances = get_ledger_balances_query.run(as_dict=True) + + for ledger_entry in ledger_balances: + account_balances_cc[ledger_entry.get("account")] = ledger_entry.get("balance") + account_balances_ac[ledger_entry.get("account")] = ledger_entry.get("balance_in_account_currency") + + for account in reversed(account_list): + parent = account.get("parent_account") + if parent: + account_balances_cc[parent] += account_balances_cc.get(account.get("name")) + + accounts_data = [ + { + "value": account.get("name"), + "company_currency": company_currency, + "balance": account_balances_cc.get(account.get("name")), + "account_currency": account.get("account_currency"), + "balance_in_account_currency": account_balances_ac.get(account.get("name")), + } + for account in account_list + ] + + return accounts_data + + def create_payment_gateway_account(gateway, payment_channel="Email", company=None): from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account