feat: Financial Ratio Report (#36130)

* feat: Financial Ratio report added

* fix: Made columns dynamic

* fix: Changed fieldtype of year column

* fix: Added Financial Ratios for all Fiscal Years

* fix: Added Validation of only Parent Having account_type of Direct Income, Indirect Income, Current Asset and Current Liability

* fix: Added 4 more ratios

* fix: added a function for repeated code

* fix: added account_type in accounts utils and cleaned report code

* fix: created function for avg_ratio_values

* fix: cleaning code

* fix: basic ratios completed

* fix: cleaned the code

* chore: code cleanup

* chore: remove comments

* chore: code cleanup

* chore: cleanup account query

* chore: Remove unused variables

---------

Co-authored-by: Ritvik Sardana <ritviksardana@Ritviks-MacBook-Air.local>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
This commit is contained in:
RitvikSardana 2023-08-04 22:05:30 +05:30 committed by GitHub
parent edbefee10c
commit b86afb2964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 575 additions and 108 deletions

View File

@ -1,67 +1,83 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Account', {
setup: function(frm) {
frm.add_fetch('parent_account', 'report_type', 'report_type');
frm.add_fetch('parent_account', 'root_type', 'root_type');
frappe.ui.form.on("Account", {
setup: function (frm) {
frm.add_fetch("parent_account", "report_type", "report_type");
frm.add_fetch("parent_account", "root_type", "root_type");
},
onload: function(frm) {
frm.set_query('parent_account', function(doc) {
onload: function (frm) {
frm.set_query("parent_account", function (doc) {
return {
filters: {
"is_group": 1,
"company": doc.company
}
is_group: 1,
company: doc.company,
},
};
});
},
refresh: function(frm) {
frm.toggle_display('account_name', frm.is_new());
refresh: function (frm) {
frm.toggle_display("account_name", frm.is_new());
// hide fields if group
frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0);
// disable fields
frm.toggle_enable(['is_group', 'company'], false);
frm.toggle_enable(["is_group", "company"], false);
if (cint(frm.doc.is_group) == 0) {
frm.toggle_display('freeze_account', frm.doc.__onload
&& frm.doc.__onload.can_freeze_account);
frm.toggle_display(
"freeze_account",
frm.doc.__onload && frm.doc.__onload.can_freeze_account
);
}
// read-only for root accounts
if (!frm.is_new()) {
if (!frm.doc.parent_account) {
frm.set_read_only();
frm.set_intro(__("This is a root account and cannot be edited."));
frm.set_intro(
__("This is a root account and cannot be edited.")
);
} else {
// credit days and type if customer or supplier
frm.set_intro(null);
frm.trigger('account_type');
frm.trigger("account_type");
// show / hide convert buttons
frm.trigger('add_toolbar_buttons');
frm.trigger("add_toolbar_buttons");
}
if (frm.has_perm('write')) {
frm.add_custom_button(__('Merge Account'), function () {
if (frm.has_perm("write")) {
frm.add_custom_button(
__("Merge Account"),
function () {
frm.trigger("merge_account");
}, __('Actions'));
frm.add_custom_button(__('Update Account Name / Number'), function () {
},
__("Actions")
);
frm.add_custom_button(
__("Update Account Name / Number"),
function () {
frm.trigger("update_account_number");
}, __('Actions'));
},
__("Actions")
);
}
}
},
account_type: function (frm) {
if (frm.doc.is_group == 0) {
frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
frm.toggle_display(["tax_rate"], frm.doc.account_type == "Tax");
frm.toggle_display("warehouse", frm.doc.account_type == "Stock");
}
},
add_toolbar_buttons: function(frm) {
frm.add_custom_button(__('Chart of Accounts'), () => {
add_toolbar_buttons: function (frm) {
frm.add_custom_button(
__("Chart of Accounts"),
() => {
frappe.set_route("Tree", "Account");
}, __('View'));
},
__("View")
);
if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Convert to Non-Group'), function () {
@ -86,31 +102,35 @@ frappe.ui.form.on('Account', {
frappe.set_route("query-report", "General Ledger");
}, __('View'));
frm.add_custom_button(__('Convert to Group'), function () {
frm.add_custom_button(
__("Convert to Group"),
function () {
return frappe.call({
doc: frm.doc,
method: 'convert_ledger_to_group',
callback: function() {
method: "convert_ledger_to_group",
callback: function () {
frm.refresh();
}
},
});
}, __('Actions'));
},
__("Actions")
);
}
},
merge_account: function(frm) {
merge_account: function (frm) {
var d = new frappe.ui.Dialog({
title: __('Merge with Existing Account'),
title: __("Merge with Existing Account"),
fields: [
{
"label" : "Name",
"fieldname": "name",
"fieldtype": "Data",
"reqd": 1,
"default": frm.doc.name
}
label: "Name",
fieldname: "name",
fieldtype: "Data",
reqd: 1,
default: frm.doc.name,
},
],
primary_action: function() {
primary_action: function () {
var data = d.get_values();
frappe.call({
method: "erpnext.accounts.doctype.account.account.merge_account",
@ -119,44 +139,47 @@ frappe.ui.form.on('Account', {
new: data.name,
is_group: frm.doc.is_group,
root_type: frm.doc.root_type,
company: frm.doc.company
company: frm.doc.company,
},
callback: function(r) {
if(!r.exc) {
if(r.message) {
callback: function (r) {
if (!r.exc) {
if (r.message) {
frappe.set_route("Form", "Account", r.message);
}
d.hide();
}
}
},
});
},
primary_action_label: __('Merge')
primary_action_label: __("Merge"),
});
d.show();
},
update_account_number: function(frm) {
update_account_number: function (frm) {
var d = new frappe.ui.Dialog({
title: __('Update Account Number / Name'),
title: __("Update Account Number / Name"),
fields: [
{
"label": "Account Name",
"fieldname": "account_name",
"fieldtype": "Data",
"reqd": 1,
"default": frm.doc.account_name
label: "Account Name",
fieldname: "account_name",
fieldtype: "Data",
reqd: 1,
default: frm.doc.account_name,
},
{
"label": "Account Number",
"fieldname": "account_number",
"fieldtype": "Data",
"default": frm.doc.account_number
}
label: "Account Number",
fieldname: "account_number",
fieldtype: "Data",
default: frm.doc.account_number,
},
],
primary_action: function() {
primary_action: function () {
var data = d.get_values();
if(data.account_number === frm.doc.account_number && data.account_name === frm.doc.account_name) {
if (
data.account_number === frm.doc.account_number &&
data.account_name === frm.doc.account_name
) {
d.hide();
return;
}
@ -166,23 +189,29 @@ frappe.ui.form.on('Account', {
args: {
account_number: data.account_number,
account_name: data.account_name,
name: frm.doc.name
name: frm.doc.name,
},
callback: function(r) {
if(!r.exc) {
if(r.message) {
callback: function (r) {
if (!r.exc) {
if (r.message) {
frappe.set_route("Form", "Account", r.message);
} else {
frm.set_value("account_number", data.account_number);
frm.set_value("account_name", data.account_name);
frm.set_value(
"account_number",
data.account_number
);
frm.set_value(
"account_name",
data.account_name
);
}
d.hide();
}
}
},
});
},
primary_action_label: __('Update')
primary_action_label: __("Update"),
});
d.show();
}
},
});

View File

@ -123,7 +123,7 @@
"label": "Account Type",
"oldfieldname": "account_type",
"oldfieldtype": "Select",
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
},
{
"description": "Rate at which this tax is applied",
@ -192,7 +192,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2023-04-11 16:08:46.983677",
"modified": "2023-07-20 18:18:44.405723",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
@ -243,7 +243,6 @@
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 1,
"share": 1,
"write": 1
}

View File

@ -45,6 +45,7 @@ class Account(NestedSet):
if frappe.local.flags.allow_unverified_charts:
return
self.validate_parent()
self.validate_parent_child_account_type()
self.validate_root_details()
validate_field_number("Account", self.name, self.account_number, self.company, "account_number")
self.validate_group_or_ledger()
@ -55,6 +56,20 @@ class Account(NestedSet):
self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children()
def validate_parent_child_account_type(self):
if self.parent_account:
if self.account_type in [
"Direct Income",
"Indirect Income",
"Current Asset",
"Current Liability",
"Direct Expense",
"Indirect Expense",
]:
parent_account_type = frappe.db.get_value("Account", self.parent_account, ["account_type"])
if parent_account_type == self.account_type:
throw(_("Only Parent can be of type {0}").format(self.account_type))
def validate_parent(self):
"""Fetch Parent Details and validate parent account"""
if self.parent_account:

View File

@ -1,22 +1,26 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements);
frappe.require("assets/erpnext/js/financial_statements.js", function () {
frappe.query_reports["Balance Sheet"] = $.extend(
{},
erpnext.financial_statements
);
erpnext.utils.add_dimensions('Balance Sheet', 10);
erpnext.utils.add_dimensions("Balance Sheet", 10);
frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "accumulated_values",
"label": __("Accumulated Values"),
"fieldtype": "Check",
"default": 1
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
});
console.log(frappe.query_reports["Balance Sheet"]["filters"]);
frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check",
"default": 1
fieldname: "include_default_book_entries",
label: __("Include Default Book Entries"),
fieldtype: "Check",
default: 1,
});
});

View File

@ -0,0 +1,72 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Financial Ratios"] = {
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1,
},
{
fieldname: "from_fiscal_year",
label: __("Start Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
reqd: 1,
},
{
fieldname: "to_fiscal_year",
label: __("End Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
reqd: 1,
},
{
fieldname: "periodicity",
label: __("Periodicity"),
fieldtype: "Data",
default: "Yearly",
reqd: 1,
hidden: 1,
},
{
fieldname: "period_start_date",
label: __("From Date"),
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
hidden: 1,
},
{
fieldname: "period_end_date",
label: __("To Date"),
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
hidden: 1,
},
],
"formatter": function(value, row, column, data, default_formatter) {
let heading_ratios = ["Liquidity Ratios", "Solvency Ratios","Turnover Ratios"]
if (heading_ratios.includes(value)) {
value = $(`<span>${value}</span>`);
let $value = $(value).css("font-weight", "bold");
value = $value.wrap("<p></p>").parent().html();
}
if (heading_ratios.includes(row[1].content) && column.fieldtype == "Float") {
column.fieldtype = "Data";
}
value = default_formatter(value, row, column, data);
return value;
},
};

View File

@ -0,0 +1,37 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2023-07-13 16:11:11.925096",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2023-07-13 16:11:11.925096",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Financial Ratios",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Account",
"report_name": "Financial Ratios",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Auditor"
},
{
"role": "Sales User"
},
{
"role": "Purchase User"
},
{
"role": "Accounts Manager"
}
]
}

View File

@ -0,0 +1,296 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import add_days, flt
from erpnext.accounts.report.financial_statements import get_data, get_period_list
from erpnext.accounts.utils import get_balance_on, get_fiscal_year
def execute(filters=None):
filters["filter_based_on"] = "Fiscal Year"
columns, data = [], []
setup_filters(filters)
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,
)
columns, years = get_columns(period_list)
data = get_ratios_data(filters, period_list, years)
return columns, data
def setup_filters(filters):
if not filters.get("period_start_date"):
period_start_date = get_fiscal_year(fiscal_year=filters.from_fiscal_year)[1]
filters["period_start_date"] = period_start_date
if not filters.get("period_end_date"):
period_end_date = get_fiscal_year(fiscal_year=filters.to_fiscal_year)[2]
filters["period_end_date"] = period_end_date
def get_columns(period_list):
years = []
columns = [
{
"label": _("Ratios"),
"fieldname": "ratio",
"fieldtype": "Data",
"width": 200,
},
]
for period in period_list:
columns.append(
{
"fieldname": period.key,
"label": period.label,
"fieldtype": "Float",
"width": 150,
}
)
years.append(period.key)
return columns, years
def get_ratios_data(filters, period_list, years):
data = []
assets, liabilities, income, expense = get_gl_data(filters, period_list, years)
current_asset, total_asset = {}, {}
current_liability, total_liability = {}, {}
net_sales, total_income = {}, {}
cogs, total_expense = {}, {}
quick_asset = {}
direct_expense = {}
for year in years:
total_quick_asset = 0
total_net_sales = 0
total_cogs = 0
for d in [
[
current_asset,
total_asset,
"Current Asset",
year,
assets,
"Asset",
quick_asset,
total_quick_asset,
],
[
current_liability,
total_liability,
"Current Liability",
year,
liabilities,
"Liability",
{},
0,
],
[cogs, total_expense, "Cost of Goods Sold", year, expense, "Expense", {}, total_cogs],
[direct_expense, direct_expense, "Direct Expense", year, expense, "Expense", {}, 0],
[net_sales, total_income, "Direct Income", year, income, "Income", {}, total_net_sales],
]:
update_balances(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7])
add_liquidity_ratios(data, years, current_asset, current_liability, quick_asset)
add_solvency_ratios(
data, years, total_asset, total_liability, net_sales, cogs, total_income, total_expense
)
add_turnover_ratios(
data, years, period_list, filters, total_asset, net_sales, cogs, direct_expense
)
return data
def get_gl_data(filters, period_list, years):
data = {}
for d in [
["Asset", "Debit"],
["Liability", "Credit"],
["Income", "Credit"],
["Expense", "Debit"],
]:
data[frappe.scrub(d[0])] = get_data(
filters.company,
d[0],
d[1],
period_list,
only_current_fiscal_year=False,
filters=filters,
)
assets, liabilities, income, expense = (
data.get("asset"),
data.get("liability"),
data.get("income"),
data.get("expense"),
)
return assets, liabilities, income, expense
def add_liquidity_ratios(data, years, current_asset, current_liability, quick_asset):
precision = frappe.db.get_single_value("System Settings", "float_precision")
data.append({"ratio": "Liquidity Ratios"})
ratio_data = [["Current Ratio", current_asset], ["Quick Ratio", quick_asset]]
for d in ratio_data:
row = {
"ratio": d[0],
}
for year in years:
row[year] = calculate_ratio(d[1].get(year, 0), current_liability.get(year, 0), precision)
data.append(row)
def add_solvency_ratios(
data, years, total_asset, total_liability, net_sales, cogs, total_income, total_expense
):
precision = frappe.db.get_single_value("System Settings", "float_precision")
data.append({"ratio": "Solvency Ratios"})
debt_equity_ratio = {"ratio": "Debt Equity Ratio"}
gross_profit_ratio = {"ratio": "Gross Profit Ratio"}
net_profit_ratio = {"ratio": "Net Profit Ratio"}
return_on_asset_ratio = {"ratio": "Return on Asset Ratio"}
return_on_equity_ratio = {"ratio": "Return on Equity Ratio"}
for year in years:
profit_after_tax = total_income[year] + total_expense[year]
share_holder_fund = total_asset[year] - total_liability[year]
debt_equity_ratio[year] = calculate_ratio(
total_liability.get(year), share_holder_fund, precision
)
return_on_equity_ratio[year] = calculate_ratio(profit_after_tax, share_holder_fund, precision)
net_profit_ratio[year] = calculate_ratio(profit_after_tax, net_sales.get(year), precision)
gross_profit_ratio[year] = calculate_ratio(
net_sales.get(year, 0) - cogs.get(year, 0), net_sales.get(year), precision
)
return_on_asset_ratio[year] = calculate_ratio(profit_after_tax, total_asset.get(year), precision)
data.append(debt_equity_ratio)
data.append(gross_profit_ratio)
data.append(net_profit_ratio)
data.append(return_on_asset_ratio)
data.append(return_on_equity_ratio)
def add_turnover_ratios(
data, years, period_list, filters, total_asset, net_sales, cogs, direct_expense
):
precision = frappe.db.get_single_value("System Settings", "float_precision")
data.append({"ratio": "Turnover Ratios"})
avg_data = {}
for d in ["Receivable", "Payable", "Stock"]:
avg_data[frappe.scrub(d)] = avg_ratio_balance("Receivable", period_list, precision, filters)
avg_debtors, avg_creditors, avg_stock = (
avg_data.get("receivable"),
avg_data.get("payable"),
avg_data.get("stock"),
)
ratio_data = [
["Fixed Asset Turnover Ratio", net_sales, total_asset],
["Debtor Turnover Ratio", net_sales, avg_debtors],
["Creditor Turnover Ratio", direct_expense, avg_creditors],
["Inventory Turnover Ratio", cogs, avg_stock],
]
for ratio in ratio_data:
row = {
"ratio": ratio[0],
}
for year in years:
row[year] = calculate_ratio(ratio[1].get(year, 0), ratio[2].get(year, 0), precision)
data.append(row)
def update_balances(
ratio_dict,
total_dict,
account_type,
year,
root_type_data,
root_type,
net_dict=None,
total_net=0,
):
for entry in root_type_data:
if not entry.get("parent_account") and entry.get("is_group"):
total_dict[year] = entry[year]
if account_type == "Direct Expense":
total_dict[year] = entry[year] * -1
if root_type in ("Asset", "Liability"):
if entry.get("account_type") == account_type and entry.get("is_group"):
ratio_dict[year] = entry.get(year)
if entry.get("account_type") in ["Bank", "Cash", "Receivable"] and not entry.get("is_group"):
total_net += entry.get(year)
net_dict[year] = total_net
elif root_type == "Income":
if entry.get("account_type") == account_type and entry.get("is_group"):
total_net += entry.get(year)
ratio_dict[year] = total_net
elif root_type == "Expense" and account_type == "Cost of Goods Sold":
if entry.get("account_type") == account_type:
total_net += entry.get(year)
ratio_dict[year] = total_net
else:
if entry.get("account_type") == account_type and entry.get("is_group"):
ratio_dict[year] = entry.get(year)
def avg_ratio_balance(account_type, period_list, precision, filters):
avg_ratio = {}
for period in period_list:
opening_date = add_days(period["from_date"], -1)
closing_date = period["to_date"]
closing_balance = get_balance_on(
date=closing_date,
company=filters.company,
account_type=account_type,
)
opening_balance = get_balance_on(
date=opening_date,
company=filters.company,
account_type=account_type,
)
avg_ratio[period["key"]] = flt(
(flt(closing_balance) + flt(opening_balance)) / 2, precision=precision
)
return avg_ratio
def calculate_ratio(value, denominator, precision):
if flt(denominator):
return flt(flt(value) / denominator, precision)
return 0

View File

@ -1,19 +1,18 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Profit and Loss Statement"] = $.extend({},
erpnext.financial_statements);
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check",
"default": 1
}
frappe.require("assets/erpnext/js/financial_statements.js", function () {
frappe.query_reports["Profit and Loss Statement"] = $.extend(
{},
erpnext.financial_statements
);
erpnext.utils.add_dimensions("Profit and Loss Statement", 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
});
});

View File

@ -179,6 +179,7 @@ def get_balance_on(
in_account_currency=True,
cost_center=None,
ignore_account_permission=False,
account_type=None,
):
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
@ -254,6 +255,21 @@ def get_balance_on(
else:
cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
if account_type:
accounts = frappe.db.get_all(
"Account",
filters={"company": company, "account_type": account_type, "is_group": 0},
pluck="name",
order_by="lft",
)
cond.append(
"""
gle.account in (%s)
"""
% (", ".join([frappe.db.escape(account) for account in accounts]))
)
if party_type and party:
cond.append(
"""gle.party_type = %s and gle.party = %s """
@ -263,7 +279,8 @@ def get_balance_on(
if company:
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account or (party_type and party):
if account or (party_type and party) or account_type:
if in_account_currency:
select_field = "sum(debit_in_account_currency) - sum(credit_in_account_currency)"
else:
@ -276,7 +293,6 @@ def get_balance_on(
select_field, " and ".join(cond)
)
)[0][0]
# if bal is None, return 0
return flt(bal)