From b86afb2964d404322ce4541f1e60d650be37a1a0 Mon Sep 17 00:00:00 2001 From: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:05:30 +0530 Subject: [PATCH] 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 Co-authored-by: Deepesh Garg --- erpnext/accounts/doctype/account/account.js | 185 ++++++----- erpnext/accounts/doctype/account/account.json | 5 +- erpnext/accounts/doctype/account/account.py | 15 + .../report/balance_sheet/balance_sheet.js | 26 +- .../report/financial_ratios/__init__.py | 0 .../financial_ratios/financial_ratios.js | 72 +++++ .../financial_ratios/financial_ratios.json | 37 +++ .../financial_ratios/financial_ratios.py | 296 ++++++++++++++++++ .../profit_and_loss_statement.js | 27 +- erpnext/accounts/utils.py | 20 +- 10 files changed, 575 insertions(+), 108 deletions(-) create mode 100644 erpnext/accounts/report/financial_ratios/__init__.py create mode 100644 erpnext/accounts/report/financial_ratios/financial_ratios.js create mode 100644 erpnext/accounts/report/financial_ratios/financial_ratios.json create mode 100644 erpnext/accounts/report/financial_ratios/financial_ratios.py diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index f033b54dd0..3c0eb85701 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -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 () { - frm.trigger("merge_account"); - }, __('Actions')); - frm.add_custom_button(__('Update Account Name / Number'), function () { - frm.trigger("update_account_number"); - }, __('Actions')); + 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 () { + frm.trigger("update_account_number"); + }, + __("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'), () => { - frappe.set_route("Tree", "Account"); - }, __('View')); + add_toolbar_buttons: function (frm) { + frm.add_custom_button( + __("Chart of Accounts"), + () => { + frappe.set_route("Tree", "Account"); + }, + __("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 () { - return frappe.call({ - doc: frm.doc, - method: 'convert_ledger_to_group', - callback: function() { - frm.refresh(); - } - }); - }, __('Actions')); + frm.add_custom_button( + __("Convert to Group"), + function () { + return frappe.call({ + doc: frm.doc, + method: "convert_ledger_to_group", + callback: function () { + frm.refresh(); + }, + }); + }, + __("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(); - } + }, }); diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index e79fb66062..78f73efff1 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -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 } diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index e94b7cf4c2..c1eca721b6 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -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: diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 4a4ad4d71c..c65b9e8ccc 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -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, }); }); diff --git a/erpnext/accounts/report/financial_ratios/__init__.py b/erpnext/accounts/report/financial_ratios/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/financial_ratios/financial_ratios.js b/erpnext/accounts/report/financial_ratios/financial_ratios.js new file mode 100644 index 0000000000..643423d865 --- /dev/null +++ b/erpnext/accounts/report/financial_ratios/financial_ratios.js @@ -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 = $(`${value}`); + let $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + } + + if (heading_ratios.includes(row[1].content) && column.fieldtype == "Float") { + column.fieldtype = "Data"; + } + + value = default_formatter(value, row, column, data); + + return value; + }, +}; diff --git a/erpnext/accounts/report/financial_ratios/financial_ratios.json b/erpnext/accounts/report/financial_ratios/financial_ratios.json new file mode 100644 index 0000000000..1a2e56bad1 --- /dev/null +++ b/erpnext/accounts/report/financial_ratios/financial_ratios.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/financial_ratios/financial_ratios.py b/erpnext/accounts/report/financial_ratios/financial_ratios.py new file mode 100644 index 0000000000..57421ebcb0 --- /dev/null +++ b/erpnext/accounts/report/financial_ratios/financial_ratios.py @@ -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 diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index e794f270c2..9fe93b9772 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -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, + }); }); diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 961f41ccef..c24442e2c3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -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)