brotherton-erpnext/erpnext/accounts/report/financial_ratios/financial_ratios.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

297 lines
7.9 KiB
Python
Raw Normal View History

# 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