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