From fb432821bfdacdad694035f3b721ca486a80bf43 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 Aug 2016 10:39:19 +0530 Subject: [PATCH 1/6] [Report] Cost center wise profitability --- .../__init__.py | 0 .../profitability_based_on_cost_center.js | 55 ++++++ .../profitability_based_on_cost_center.json | 18 ++ .../profitability_based_on_cost_center.py | 186 ++++++++++++++++++ 4 files changed, 259 insertions(+) create mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/__init__.py create mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js create mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json create mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/__init__.py b/erpnext/accounts/report/profitability_based_on_cost_center/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js new file mode 100644 index 0000000000..c66cc4cca8 --- /dev/null +++ b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js @@ -0,0 +1,55 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Profitability based on Cost Center"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + query_report.filters_by_name.from_date.set_input(fy.year_start_date); + query_report.filters_by_name.to_date.set_input(fy.year_end_date); + query_report.trigger_refresh(); + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_start_date"), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_end_date"), + } + ], + "formatter": erpnext.financial_statements.formatter, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 + } +}); + diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json new file mode 100644 index 0000000000..cc29860632 --- /dev/null +++ b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json @@ -0,0 +1,18 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2016-08-23 03:12:00.957918", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2016-08-23 03:12:00.957918", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Profitability based on Cost Center", + "owner": "Administrator", + "ref_doctype": "GL Entry", + "report_name": "Profitability based on Cost Center", + "report_type": "Script Report" +} \ No newline at end of file diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py new file mode 100644 index 0000000000..095936d4b8 --- /dev/null +++ b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py @@ -0,0 +1,186 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, getdate, formatdate, cstr +from erpnext.accounts.report.financial_statements import filter_accounts +from erpnext.accounts.report.trial_balance.trial_balance import validate_filters + +value_fields = ("income", "expense", "total") + +def execute(filters=None): + validate_filters(filters) + data = get_data(filters) + columns = get_columns() + return columns, data + +def get_data(filters): + accounts = frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt + from `tabCost Center` where company=%s order by lft""", filters.company, as_dict=True) + + if not accounts: + return None + + accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) + + min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabCost Center` + where company=%s""", (filters.company,))[0] + + gl_entries_by_account = {} + + set_gl_entries_by_account(filters.company, filters.from_date, + filters.to_date, min_lft, max_rgt, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) + + total_row = calculate_values(accounts, gl_entries_by_account, filters) + accumulate_values_into_parents(accounts, accounts_by_name) + + data = prepare_data(accounts, filters, total_row, parent_children_map) + + return data + +def calculate_values(accounts, gl_entries_by_account, filters): + init = { + "income": 0.0, + "expense": 0.0, + "total": 0.0 + } + + total_row = { + "cost_center": None, + "account_name": "'" + _("Total") + "'", + "warn_if_negative": True, + "income": 0.0, + "expense": 0.0, + "total": 0.0 + } + + for d in accounts: + d.update(init.copy()) + + # add opening + + for entry in gl_entries_by_account.get(d.name, []): + if cstr(entry.is_opening) != "Yes": + if entry.type == 'Income': + d["income"] += flt(entry.credit) - flt(entry.debit) + if entry.type == 'Expense': + d["expense"] += flt(entry.debit) - flt(entry.credit) + + d["total"] = d.get("income") - d.get("expense") + + total_row["income"] += d["income"] + total_row["expense"] += d["expense"] + + total_row["total"] = total_row.get("income") - total_row.get("expense") + + return total_row + +def accumulate_values_into_parents(accounts, accounts_by_name): + for d in reversed(accounts): + if d.parent_account: + for key in value_fields: + accounts_by_name[d.parent_account][key] += d[key] + +def prepare_data(accounts, filters, total_row, parent_children_map): + data = [] + company_currency = frappe.db.get_value("Company", filters.company, "default_currency") + + for d in accounts: + has_value = False + row = { + "account_name": d.account_name, + "account": d.name, + "parent_account": d.parent_account, + "indent": d.indent, + "from_date": filters.from_date, + "to_date": filters.to_date, + "currency": company_currency + } + + for key in value_fields: + row[key] = flt(d.get(key, 0.0), 3) + + if abs(row[key]) >= 0.005: + # ignore zero values + has_value = True + + row["has_value"] = has_value + data.append(row) + + data.extend([{},total_row]) + + return data + +def get_columns(): + return [ + { + "fieldname": "account", + "label": _("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + "width": 300 + }, + { + "fieldname": "income", + "label": _("Income"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "expense", + "label": _("Expense"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "total", + "label": _("Gross Profit / Loss"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1 + } + ] + +def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, gl_entries_by_account, + ignore_closing_entries=False): + """Returns a dict like { "account": [gl entries], ... }""" + additional_conditions = [] + + if ignore_closing_entries: + additional_conditions.append("and ifnull(voucher_type, '')!='Period Closing Voucher'") + + if from_date: + additional_conditions.append("and posting_date >= %(from_date)s") + + gl_entries = frappe.db.sql("""select posting_date, cost_center, debit, credit, + is_opening, (select root_type from `tabAccount` where name = account) as type + from `tabGL Entry` where company=%(company)s + {additional_conditions} + and posting_date <= %(to_date)s + and cost_center in (select name from `tabCost Center` + where lft >= %(lft)s and rgt <= %(rgt)s) + order by cost_center, posting_date""".format(additional_conditions="\n".join(additional_conditions)), + { + "company": company, + "from_date": from_date, + "to_date": to_date, + "lft": root_lft, + "rgt": root_rgt + }, + as_dict=True) + + for entry in gl_entries: + gl_entries_by_account.setdefault(entry.cost_center, []).append(entry) + + return gl_entries_by_account \ No newline at end of file From 78e08b8af5aba6e39504f462459e31eac5d26bba Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Aug 2016 16:22:30 +0530 Subject: [PATCH 2/6] [Report] Profitability Analysis --- .../report/profitability_analysis/__init__.py | 0 .../profitability_analysis.js | 63 ++++++ .../profitability_analysis.json | 18 ++ .../profitability_analysis.py | 186 ++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 erpnext/accounts/report/profitability_analysis/__init__.py create mode 100644 erpnext/accounts/report/profitability_analysis/profitability_analysis.js create mode 100644 erpnext/accounts/report/profitability_analysis/profitability_analysis.json create mode 100644 erpnext/accounts/report/profitability_analysis/profitability_analysis.py diff --git a/erpnext/accounts/report/profitability_analysis/__init__.py b/erpnext/accounts/report/profitability_analysis/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js new file mode 100644 index 0000000000..d0f328e080 --- /dev/null +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -0,0 +1,63 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Profitability Analysis"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "based_on", + "label": __("Baed On"), + "fieldtype": "Select", + "options": "Cost Center\nProject", + "default": "Cost Center", + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + query_report.filters_by_name.from_date.set_input(fy.year_start_date); + query_report.filters_by_name.to_date.set_input(fy.year_end_date); + query_report.trigger_refresh(); + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_start_date"), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_end_date"), + } + ], + "formatter": erpnext.financial_statements.formatter, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 + } +}); + diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.json b/erpnext/accounts/report/profitability_analysis/profitability_analysis.json new file mode 100644 index 0000000000..d9c00c6f41 --- /dev/null +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.json @@ -0,0 +1,18 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2016-08-24 05:47:09.931202", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2016-08-24 05:47:09.931202", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Profitability Analysis", + "owner": "Administrator", + "ref_doctype": "GL Entry", + "report_name": "Profitability Analysis", + "report_type": "Script Report" +} \ No newline at end of file diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py new file mode 100644 index 0000000000..095936d4b8 --- /dev/null +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -0,0 +1,186 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, getdate, formatdate, cstr +from erpnext.accounts.report.financial_statements import filter_accounts +from erpnext.accounts.report.trial_balance.trial_balance import validate_filters + +value_fields = ("income", "expense", "total") + +def execute(filters=None): + validate_filters(filters) + data = get_data(filters) + columns = get_columns() + return columns, data + +def get_data(filters): + accounts = frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt + from `tabCost Center` where company=%s order by lft""", filters.company, as_dict=True) + + if not accounts: + return None + + accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) + + min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabCost Center` + where company=%s""", (filters.company,))[0] + + gl_entries_by_account = {} + + set_gl_entries_by_account(filters.company, filters.from_date, + filters.to_date, min_lft, max_rgt, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) + + total_row = calculate_values(accounts, gl_entries_by_account, filters) + accumulate_values_into_parents(accounts, accounts_by_name) + + data = prepare_data(accounts, filters, total_row, parent_children_map) + + return data + +def calculate_values(accounts, gl_entries_by_account, filters): + init = { + "income": 0.0, + "expense": 0.0, + "total": 0.0 + } + + total_row = { + "cost_center": None, + "account_name": "'" + _("Total") + "'", + "warn_if_negative": True, + "income": 0.0, + "expense": 0.0, + "total": 0.0 + } + + for d in accounts: + d.update(init.copy()) + + # add opening + + for entry in gl_entries_by_account.get(d.name, []): + if cstr(entry.is_opening) != "Yes": + if entry.type == 'Income': + d["income"] += flt(entry.credit) - flt(entry.debit) + if entry.type == 'Expense': + d["expense"] += flt(entry.debit) - flt(entry.credit) + + d["total"] = d.get("income") - d.get("expense") + + total_row["income"] += d["income"] + total_row["expense"] += d["expense"] + + total_row["total"] = total_row.get("income") - total_row.get("expense") + + return total_row + +def accumulate_values_into_parents(accounts, accounts_by_name): + for d in reversed(accounts): + if d.parent_account: + for key in value_fields: + accounts_by_name[d.parent_account][key] += d[key] + +def prepare_data(accounts, filters, total_row, parent_children_map): + data = [] + company_currency = frappe.db.get_value("Company", filters.company, "default_currency") + + for d in accounts: + has_value = False + row = { + "account_name": d.account_name, + "account": d.name, + "parent_account": d.parent_account, + "indent": d.indent, + "from_date": filters.from_date, + "to_date": filters.to_date, + "currency": company_currency + } + + for key in value_fields: + row[key] = flt(d.get(key, 0.0), 3) + + if abs(row[key]) >= 0.005: + # ignore zero values + has_value = True + + row["has_value"] = has_value + data.append(row) + + data.extend([{},total_row]) + + return data + +def get_columns(): + return [ + { + "fieldname": "account", + "label": _("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + "width": 300 + }, + { + "fieldname": "income", + "label": _("Income"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "expense", + "label": _("Expense"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "total", + "label": _("Gross Profit / Loss"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1 + } + ] + +def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, gl_entries_by_account, + ignore_closing_entries=False): + """Returns a dict like { "account": [gl entries], ... }""" + additional_conditions = [] + + if ignore_closing_entries: + additional_conditions.append("and ifnull(voucher_type, '')!='Period Closing Voucher'") + + if from_date: + additional_conditions.append("and posting_date >= %(from_date)s") + + gl_entries = frappe.db.sql("""select posting_date, cost_center, debit, credit, + is_opening, (select root_type from `tabAccount` where name = account) as type + from `tabGL Entry` where company=%(company)s + {additional_conditions} + and posting_date <= %(to_date)s + and cost_center in (select name from `tabCost Center` + where lft >= %(lft)s and rgt <= %(rgt)s) + order by cost_center, posting_date""".format(additional_conditions="\n".join(additional_conditions)), + { + "company": company, + "from_date": from_date, + "to_date": to_date, + "lft": root_lft, + "rgt": root_rgt + }, + as_dict=True) + + for entry in gl_entries: + gl_entries_by_account.setdefault(entry.cost_center, []).append(entry) + + return gl_entries_by_account \ No newline at end of file From 1255a6627e1892606d510a8388c11e4ecce56976 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Aug 2016 17:56:09 +0530 Subject: [PATCH 3/6] project based profitability Analysis --- .../profitability_analysis.js | 5 ++ .../profitability_analysis.py | 51 ++++++++++--------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index d0f328e080..9c698f38d2 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -51,6 +51,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("To Date"), "fieldtype": "Date", "default": frappe.defaults.get_user_default("year_end_date"), + }, + { + "fieldname": "show_zero_values", + "label": __("Show zero values"), + "fieldtype": "Check" } ], "formatter": erpnext.financial_statements.formatter, diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index 095936d4b8..c81b4f264e 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -5,38 +5,43 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt, getdate, formatdate, cstr -from erpnext.accounts.report.financial_statements import filter_accounts +from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows from erpnext.accounts.report.trial_balance.trial_balance import validate_filters value_fields = ("income", "expense", "total") def execute(filters=None): + based_on = filters.based_on.replace(' ', '_').lower() validate_filters(filters) - data = get_data(filters) - columns = get_columns() + accounts = get_accounts_data(based_on, filters.company) + data = get_data(accounts, filters, based_on) + columns = get_columns(filters) return columns, data -def get_data(filters): - accounts = frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt - from `tabCost Center` where company=%s order by lft""", filters.company, as_dict=True) +def get_accounts_data(based_on, company): + if based_on == 'cost_center': + return frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt + from `tabCost Center` where company=%s order by lft""", company, as_dict=True) + else: + return frappe.get_all('Project', fields = ["name"], filters = {'company': company}) +def get_data(accounts, filters, based_on): if not accounts: return None accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) - min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabCost Center` - where company=%s""", (filters.company,))[0] - gl_entries_by_account = {} set_gl_entries_by_account(filters.company, filters.from_date, - filters.to_date, min_lft, max_rgt, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) + filters.to_date, based_on, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) total_row = calculate_values(accounts, gl_entries_by_account, filters) accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, total_row, parent_children_map) + data = filter_out_zero_value_rows(data, parent_children_map, + show_zero_values=filters.get("show_zero_values")) return data @@ -90,13 +95,14 @@ def prepare_data(accounts, filters, total_row, parent_children_map): for d in accounts: has_value = False row = { - "account_name": d.account_name, + "account_name": d.account_name or d.name, "account": d.name, "parent_account": d.parent_account, "indent": d.indent, "from_date": filters.from_date, "to_date": filters.to_date, - "currency": company_currency + "currency": company_currency, + "based_on": filters.based_on } for key in value_fields: @@ -113,13 +119,13 @@ def prepare_data(accounts, filters, total_row, parent_children_map): return data -def get_columns(): +def get_columns(filters): return [ { "fieldname": "account", - "label": _("Cost Center"), + "label": _(filters.based_on), "fieldtype": "Link", - "options": "Cost Center", + "options": filters.based_on, "width": 300 }, { @@ -152,7 +158,7 @@ def get_columns(): } ] -def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, gl_entries_by_account, +def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_by_account, ignore_closing_entries=False): """Returns a dict like { "account": [gl entries], ... }""" additional_conditions = [] @@ -163,24 +169,21 @@ def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, g if from_date: additional_conditions.append("and posting_date >= %(from_date)s") - gl_entries = frappe.db.sql("""select posting_date, cost_center, debit, credit, + gl_entries = frappe.db.sql("""select posting_date, {based_on} as based_on, debit, credit, is_opening, (select root_type from `tabAccount` where name = account) as type from `tabGL Entry` where company=%(company)s {additional_conditions} and posting_date <= %(to_date)s - and cost_center in (select name from `tabCost Center` - where lft >= %(lft)s and rgt <= %(rgt)s) - order by cost_center, posting_date""".format(additional_conditions="\n".join(additional_conditions)), + and {based_on} is not null + order by {based_on}, posting_date""".format(additional_conditions="\n".join(additional_conditions), based_on= based_on), { "company": company, "from_date": from_date, - "to_date": to_date, - "lft": root_lft, - "rgt": root_rgt + "to_date": to_date }, as_dict=True) for entry in gl_entries: - gl_entries_by_account.setdefault(entry.cost_center, []).append(entry) + gl_entries_by_account.setdefault(entry.based_on, []).append(entry) return gl_entries_by_account \ No newline at end of file From 5794ffd43d9c4c41fa0f7f30787835a4587b4ec3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Aug 2016 19:12:14 +0530 Subject: [PATCH 4/6] added filters project and cost center in profit and loss statement, open profit and loss statement on click of cost center/project in profitability analysis --- .../accounts/report/financial_statements.py | 31 +++++++++----- .../profit_and_loss_statement.js | 24 ++++++++--- .../profit_and_loss_statement.py | 4 +- .../profitability_analysis.js | 40 ++++++++++++++++++- .../profitability_analysis.py | 9 ++--- .../report/trial_balance/trial_balance.py | 2 +- 6 files changed, 86 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index f6968a1c3b..01853a7246 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -104,7 +104,7 @@ def get_label(periodicity, from_date, to_date): return label -def get_data(company, root_type, balance_must_be, period_list, +def get_data(company, root_type, balance_must_be, period_list, filters=None, accumulated_values=1, only_current_fiscal_year=True, ignore_closing_entries=False, ignore_accumulated_values_for_fy=False): accounts = get_accounts(company, root_type) @@ -122,7 +122,7 @@ def get_data(company, root_type, balance_must_be, period_list, set_gl_entries_by_account(company, period_list[0]["year_start_date"] if only_current_fiscal_year else None, period_list[-1]["to_date"], - root.lft, root.rgt, + root.lft, root.rgt, filters, gl_entries_by_account, ignore_closing_entries=ignore_closing_entries) calculate_values(accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy) @@ -288,16 +288,11 @@ def sort_root_accounts(roots): roots.sort(compare_roots) -def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, gl_entries_by_account, +def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account, ignore_closing_entries=False): """Returns a dict like { "account": [gl entries], ... }""" - additional_conditions = [] - if ignore_closing_entries: - additional_conditions.append("and ifnull(voucher_type, '')!='Period Closing Voucher'") - - if from_date: - additional_conditions.append("and posting_date >= %(from_date)s") + additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters) gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening from `tabGL Entry` where company=%(company)s @@ -305,7 +300,7 @@ def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, g and posting_date <= %(to_date)s and account in (select name from `tabAccount` where lft >= %(lft)s and rgt <= %(rgt)s) - order by account, posting_date""".format(additional_conditions="\n".join(additional_conditions)), + order by account, posting_date""".format(additional_conditions=additional_conditions), { "company": company, "from_date": from_date, @@ -320,6 +315,22 @@ def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, g return gl_entries_by_account +def get_additional_conditions(from_date, ignore_closing_entries, filters): + additional_conditions = [] + + if ignore_closing_entries: + additional_conditions.append("ifnull(voucher_type, '')!='Period Closing Voucher'") + + if from_date: + additional_conditions.append("posting_date >= %(from_date)s") + + if filters: + for key in filters: + if filters.get(key) and key in ['cost_center', 'project']: + additional_conditions.append("%s = '%s'"%(key, filters.get(key))) + + return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" + def get_columns(periodicity, period_list, accumulated_values=1, company=None): columns = [{ "fieldname": "account", 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 0f7a6b6ef3..bcac2dffa4 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 @@ -5,9 +5,23 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { frappe.query_reports["Profit and Loss Statement"] = $.extend({}, erpnext.financial_statements); - frappe.query_reports["Profit and Loss Statement"]["filters"].push({ - "fieldname": "accumulated_values", - "label": __("Accumulated Values"), - "fieldtype": "Check" - }); + frappe.query_reports["Profit and Loss Statement"]["filters"].push( + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center" + }, + { + "fieldname":"project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" + }, + { + "fieldname": "accumulated_values", + "label": __("Accumulated Values"), + "fieldtype": "Check" + } + ); }); \ No newline at end of file diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index 3629d4de76..6b7f49041a 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -10,9 +10,9 @@ from erpnext.accounts.report.financial_statements import (get_period_list, get_c def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.periodicity) - income = get_data(filters.company, "Income", "Credit", period_list, + income = get_data(filters.company, "Income", "Credit", period_list, filters = filters, accumulated_values=filters.accumulated_values, ignore_closing_entries=True, ignore_accumulated_values_for_fy= True) - expense = get_data(filters.company, "Expense", "Debit", period_list, + expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters, accumulated_values=filters.accumulated_values, ignore_closing_entries=True, ignore_accumulated_values_for_fy= True) net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 9c698f38d2..48924b5d91 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -58,7 +58,45 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Check" } ], - "formatter": erpnext.financial_statements.formatter, + "formatter": function(row, cell, value, columnDef, dataContext, default_formatter) { + if (columnDef.df.fieldname=="account") { + value = dataContext.account_name; + + columnDef.df.link_onclick = + "frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(dataContext) + ")"; + columnDef.df.is_tree = true; + } + + value = default_formatter(row, cell, value, columnDef, dataContext); + + if (!dataContext.parent_account && dataContext.based_on != 'project') { + var $value = $(value).css("font-weight", "bold"); + if (dataContext.warn_if_negative && dataContext[columnDef.df.fieldname] < 0) { + $value.addClass("text-danger"); + } + + value = $value.wrap("

").parent().html(); + } + + return value; + }, + "open_profit_and_loss_statement": function(data) { + if (!data.account) return; + + frappe.route_options = { + "company": frappe.query_report.filters_by_name.company.get_value(), + "from_fiscal_year": data.fiscal_year, + "to_fiscal_year": data.fiscal_year + }; + + if(data.based_on == 'cost_center'){ + frappe.route_options["cost_center"] = data.account + } else { + frappe.route_options["project"] = data.account + } + + frappe.set_route("query-report", "Profit and Loss Statement"); + }, "tree": true, "name_field": "account", "parent_field": "parent_account", diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index c81b4f264e..7277dc2461 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -39,7 +39,7 @@ def get_data(accounts, filters, based_on): total_row = calculate_values(accounts, gl_entries_by_account, filters) accumulate_values_into_parents(accounts, accounts_by_name) - data = prepare_data(accounts, filters, total_row, parent_children_map) + data = prepare_data(accounts, filters, total_row, parent_children_map, based_on) data = filter_out_zero_value_rows(data, parent_children_map, show_zero_values=filters.get("show_zero_values")) @@ -88,7 +88,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name): for key in value_fields: accounts_by_name[d.parent_account][key] += d[key] -def prepare_data(accounts, filters, total_row, parent_children_map): +def prepare_data(accounts, filters, total_row, parent_children_map, based_on): data = [] company_currency = frappe.db.get_value("Company", filters.company, "default_currency") @@ -99,10 +99,9 @@ def prepare_data(accounts, filters, total_row, parent_children_map): "account": d.name, "parent_account": d.parent_account, "indent": d.indent, - "from_date": filters.from_date, - "to_date": filters.to_date, + "fiscal_year": filters.fiscal_year, "currency": company_currency, - "based_on": filters.based_on + "based_on": based_on } for key in value_fields: diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index a51e9b02eb..5401902443 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -65,7 +65,7 @@ def get_data(filters): gl_entries_by_account = {} set_gl_entries_by_account(filters.company, filters.from_date, - filters.to_date, min_lft, max_rgt, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) + filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) opening_balances = get_opening_balances(filters) From 1e8732abc290f75162164a475492e96f1dd42a7a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Aug 2016 19:20:58 +0530 Subject: [PATCH 5/6] removed old report --- .../__init__.py | 0 .../profitability_based_on_cost_center.js | 55 ------ .../profitability_based_on_cost_center.json | 18 -- .../profitability_based_on_cost_center.py | 186 ------------------ 4 files changed, 259 deletions(-) delete mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/__init__.py delete mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js delete mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json delete mode 100644 erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/__init__.py b/erpnext/accounts/report/profitability_based_on_cost_center/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js deleted file mode 100644 index c66cc4cca8..0000000000 --- a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Profitability based on Cost Center"] = { - "filters": [ - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1, - "on_change": function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - query_report.filters_by_name.from_date.set_input(fy.year_start_date); - query_report.filters_by_name.to_date.set_input(fy.year_end_date); - query_report.trigger_refresh(); - }); - } - }, - { - "fieldname": "from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": frappe.defaults.get_user_default("year_start_date"), - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": frappe.defaults.get_user_default("year_end_date"), - } - ], - "formatter": erpnext.financial_statements.formatter, - "tree": true, - "name_field": "account", - "parent_field": "parent_account", - "initial_depth": 3 - } -}); - diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json deleted file mode 100644 index cc29860632..0000000000 --- a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2016-08-23 03:12:00.957918", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2016-08-23 03:12:00.957918", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Profitability based on Cost Center", - "owner": "Administrator", - "ref_doctype": "GL Entry", - "report_name": "Profitability based on Cost Center", - "report_type": "Script Report" -} \ No newline at end of file diff --git a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py b/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py deleted file mode 100644 index 095936d4b8..0000000000 --- a/erpnext/accounts/report/profitability_based_on_cost_center/profitability_based_on_cost_center.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils import flt, getdate, formatdate, cstr -from erpnext.accounts.report.financial_statements import filter_accounts -from erpnext.accounts.report.trial_balance.trial_balance import validate_filters - -value_fields = ("income", "expense", "total") - -def execute(filters=None): - validate_filters(filters) - data = get_data(filters) - columns = get_columns() - return columns, data - -def get_data(filters): - accounts = frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt - from `tabCost Center` where company=%s order by lft""", filters.company, as_dict=True) - - if not accounts: - return None - - accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) - - min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabCost Center` - where company=%s""", (filters.company,))[0] - - gl_entries_by_account = {} - - set_gl_entries_by_account(filters.company, filters.from_date, - filters.to_date, min_lft, max_rgt, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) - - total_row = calculate_values(accounts, gl_entries_by_account, filters) - accumulate_values_into_parents(accounts, accounts_by_name) - - data = prepare_data(accounts, filters, total_row, parent_children_map) - - return data - -def calculate_values(accounts, gl_entries_by_account, filters): - init = { - "income": 0.0, - "expense": 0.0, - "total": 0.0 - } - - total_row = { - "cost_center": None, - "account_name": "'" + _("Total") + "'", - "warn_if_negative": True, - "income": 0.0, - "expense": 0.0, - "total": 0.0 - } - - for d in accounts: - d.update(init.copy()) - - # add opening - - for entry in gl_entries_by_account.get(d.name, []): - if cstr(entry.is_opening) != "Yes": - if entry.type == 'Income': - d["income"] += flt(entry.credit) - flt(entry.debit) - if entry.type == 'Expense': - d["expense"] += flt(entry.debit) - flt(entry.credit) - - d["total"] = d.get("income") - d.get("expense") - - total_row["income"] += d["income"] - total_row["expense"] += d["expense"] - - total_row["total"] = total_row.get("income") - total_row.get("expense") - - return total_row - -def accumulate_values_into_parents(accounts, accounts_by_name): - for d in reversed(accounts): - if d.parent_account: - for key in value_fields: - accounts_by_name[d.parent_account][key] += d[key] - -def prepare_data(accounts, filters, total_row, parent_children_map): - data = [] - company_currency = frappe.db.get_value("Company", filters.company, "default_currency") - - for d in accounts: - has_value = False - row = { - "account_name": d.account_name, - "account": d.name, - "parent_account": d.parent_account, - "indent": d.indent, - "from_date": filters.from_date, - "to_date": filters.to_date, - "currency": company_currency - } - - for key in value_fields: - row[key] = flt(d.get(key, 0.0), 3) - - if abs(row[key]) >= 0.005: - # ignore zero values - has_value = True - - row["has_value"] = has_value - data.append(row) - - data.extend([{},total_row]) - - return data - -def get_columns(): - return [ - { - "fieldname": "account", - "label": _("Cost Center"), - "fieldtype": "Link", - "options": "Cost Center", - "width": 300 - }, - { - "fieldname": "income", - "label": _("Income"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "expense", - "label": _("Expense"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "total", - "label": _("Gross Profit / Loss"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "currency", - "label": _("Currency"), - "fieldtype": "Link", - "options": "Currency", - "hidden": 1 - } - ] - -def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, gl_entries_by_account, - ignore_closing_entries=False): - """Returns a dict like { "account": [gl entries], ... }""" - additional_conditions = [] - - if ignore_closing_entries: - additional_conditions.append("and ifnull(voucher_type, '')!='Period Closing Voucher'") - - if from_date: - additional_conditions.append("and posting_date >= %(from_date)s") - - gl_entries = frappe.db.sql("""select posting_date, cost_center, debit, credit, - is_opening, (select root_type from `tabAccount` where name = account) as type - from `tabGL Entry` where company=%(company)s - {additional_conditions} - and posting_date <= %(to_date)s - and cost_center in (select name from `tabCost Center` - where lft >= %(lft)s and rgt <= %(rgt)s) - order by cost_center, posting_date""".format(additional_conditions="\n".join(additional_conditions)), - { - "company": company, - "from_date": from_date, - "to_date": to_date, - "lft": root_lft, - "rgt": root_rgt - }, - as_dict=True) - - for entry in gl_entries: - gl_entries_by_account.setdefault(entry.cost_center, []).append(entry) - - return gl_entries_by_account \ No newline at end of file From 6462ba4e0b109434feac9e3353a71535c92f9234 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 26 Aug 2016 12:39:54 +0530 Subject: [PATCH 6/6] minor changes --- erpnext/accounts/report/financial_statements.py | 4 ++-- .../profitability_analysis/profitability_analysis.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 01853a7246..09174281bf 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -325,8 +325,8 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions.append("posting_date >= %(from_date)s") if filters: - for key in filters: - if filters.get(key) and key in ['cost_center', 'project']: + for key in ['cost_center', 'project']: + if filters.get(key): additional_conditions.append("%s = '%s'"%(key, filters.get(key))) return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index 7277dc2461..4f9fd15f66 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -8,7 +8,7 @@ from frappe.utils import flt, getdate, formatdate, cstr from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows from erpnext.accounts.report.trial_balance.trial_balance import validate_filters -value_fields = ("income", "expense", "total") +value_fields = ("income", "expense", "gross_profit_loss") def execute(filters=None): based_on = filters.based_on.replace(' ', '_').lower() @@ -49,7 +49,7 @@ def calculate_values(accounts, gl_entries_by_account, filters): init = { "income": 0.0, "expense": 0.0, - "total": 0.0 + "gross_profit_loss": 0.0 } total_row = { @@ -58,7 +58,7 @@ def calculate_values(accounts, gl_entries_by_account, filters): "warn_if_negative": True, "income": 0.0, "expense": 0.0, - "total": 0.0 + "gross_profit_loss": 0.0 } for d in accounts: @@ -73,12 +73,12 @@ def calculate_values(accounts, gl_entries_by_account, filters): if entry.type == 'Expense': d["expense"] += flt(entry.debit) - flt(entry.credit) - d["total"] = d.get("income") - d.get("expense") + d["gross_profit_loss"] = d.get("income") - d.get("expense") total_row["income"] += d["income"] total_row["expense"] += d["expense"] - total_row["total"] = total_row.get("income") - total_row.get("expense") + total_row["gross_profit_loss"] = total_row.get("income") - total_row.get("expense") return total_row @@ -142,7 +142,7 @@ def get_columns(filters): "width": 120 }, { - "fieldname": "total", + "fieldname": "gross_profit_loss", "label": _("Gross Profit / Loss"), "fieldtype": "Currency", "options": "currency",