From 059f1e09a8252e9b31718b5449bcfc2304e95349 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 14 Jul 2014 19:06:52 +0530 Subject: [PATCH 1/7] Balance Sheet - first cut --- .../doctype/fiscal_year/fiscal_year.json | 6 +- .../accounts/report/balance_sheet/__init__.py | 0 .../report/balance_sheet/balance_sheet.js | 31 ++++++ .../report/balance_sheet/balance_sheet.json | 15 +++ .../report/balance_sheet/balance_sheet.py | 99 +++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 erpnext/accounts/report/balance_sheet/__init__.py create mode 100644 erpnext/accounts/report/balance_sheet/balance_sheet.js create mode 100644 erpnext/accounts/report/balance_sheet/balance_sheet.json create mode 100644 erpnext/accounts/report/balance_sheet/balance_sheet.py diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json index dcd5a7608c..0f7aefd312 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json @@ -55,7 +55,7 @@ ], "icon": "icon-calendar", "idx": 1, - "modified": "2014-05-27 03:49:10.942338", + "modified": "2014-07-14 05:30:56.843180", "modified_by": "Administrator", "module": "Accounts", "name": "Fiscal Year", @@ -82,5 +82,7 @@ "read": 1, "role": "All" } - ] + ], + "sort_field": "name", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/report/balance_sheet/__init__.py b/erpnext/accounts/report/balance_sheet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js new file mode 100644 index 0000000000..9bdd298077 --- /dev/null +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -0,0 +1,31 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.query_reports["Balance Sheet"] = { + "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 + }, + { + "fieldname": "periodicity", + "label": __("Periodicity"), + "fieldtype": "Select", + "options": "Yearly\nQuarterly\nMonthly", + "default": "Yearly", + "reqd": 1 + } + ] +} diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.json b/erpnext/accounts/report/balance_sheet/balance_sheet.json new file mode 100644 index 0000000000..af299bb29c --- /dev/null +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.json @@ -0,0 +1,15 @@ +{ + "apply_user_permissions": 1, + "creation": "2014-07-14 05:24:20.385279", + "docstatus": 0, + "doctype": "Report", + "is_standard": "Yes", + "modified": "2014-07-14 05:24:20.385279", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Balance Sheet", + "owner": "Administrator", + "ref_doctype": "GL Entry", + "report_name": "Balance Sheet", + "report_type": "Script Report" +} \ No newline at end of file diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py new file mode 100644 index 0000000000..dc71e92138 --- /dev/null +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -0,0 +1,99 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import cstr, flt +from frappe import _ + +def execute(filters=None): + company = filters.company + fiscal_year = filters.fiscal_year + depth = 3 + end_date = frappe.db.get_value("Fiscal Year", fiscal_year, "year_end_date") + + for root_type, balance_must_be in (("Asset", "Debit"), ("Liability", "Credit"), ("Equity", "Credit")): + accounts, account_gl_entries = get_accounts_and_gl_entries(root_type, company, end_date) + if accounts: + accounts, accounts_map = filter_accounts(accounts, depth=depth) + + for d in accounts: + d.debit = d.credit = 0 + for account_name in ([d.name] + (d.invisible_children or [])): + for each in account_gl_entries.get(account_name, []): + d.debit += flt(each.debit) + d.credit += flt(each.credit) + + for d in reversed(accounts): + if d.parent_account: + accounts_map[d.parent_account]["debit"] += d.debit + accounts_map[d.parent_account]["credit"] += d.credit + + for d in accounts: + d.balance = d["debit"] - d["credit"] + if d.balance: + d.balance *= (1 if balance_must_be=="Debit" else -1) + print (" " * d["indent"] * 2) + d["account_name"], d["balance"], balance_must_be + + return [], [] + +def get_accounts_and_gl_entries(root_type, company, end_date): + # root lft, rgt + root_account = frappe.db.sql("""select lft, rgt from `tabAccount` + where company=%s and root_type=%s order by lft limit 1""", + (company, root_type), as_dict=True) + + if not root_account: + return None, None + + lft, rgt = root_account[0].lft, root_account[0].rgt + + accounts = frappe.db.sql("""select * from `tabAccount` + where company=%(company)s and lft >= %(lft)s and rgt <= %(rgt)s order by lft""", + { "company": company, "lft": lft, "rgt": rgt }, as_dict=True) + + gl_entries = frappe.db.sql("""select * from `tabGL Entry` + where company=%(company)s + and posting_date <= %(end_date)s + and account in (select name from `tabAccount` + where lft >= %(lft)s and rgt <= %(rgt)s)""", + { + "company": company, + "end_date": end_date, + "lft": lft, + "rgt": rgt + }, + as_dict=True) + + account_gl_entries = {} + for entry in gl_entries: + account_gl_entries.setdefault(entry.account, []).append(entry) + + return accounts, account_gl_entries + +def filter_accounts(accounts, depth): + parent_children_map = {} + accounts_map = {} + for d in accounts: + accounts_map[d.name] = d + parent_children_map.setdefault(d.parent_account or None, []).append(d) + + data = [] + def add_to_data(parent, level): + if level < depth: + for child in (parent_children_map.get(parent) or []): + child.indent = level + data.append(child) + add_to_data(child.name, level + 1) + + else: + # include all children at level lower than the depth + parent_account = accounts_map[parent] + parent_account["invisible_children"] = [] + for d in accounts: + if d.lft > parent_account.lft and d.rgt < parent_account.rgt: + parent_account["invisible_children"].append(d.name) + + add_to_data(None, 0) + + return data, accounts_map From fa576a22e333b192cdfcdc8f82626288812edba6 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 17 Jul 2014 19:12:28 +0530 Subject: [PATCH 2/7] Balance Sheet --- .../report/balance_sheet/balance_sheet.html | 46 +++++++ .../report/balance_sheet/balance_sheet.js | 44 ++++++- .../report/balance_sheet/balance_sheet.py | 120 +++++++++++++++--- erpnext/config/accounts.py | 6 + 4 files changed, 199 insertions(+), 17 deletions(-) create mode 100644 erpnext/accounts/report/balance_sheet/balance_sheet.html diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.html b/erpnext/accounts/report/balance_sheet/balance_sheet.html new file mode 100644 index 0000000000..a6a33f594e --- /dev/null +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.html @@ -0,0 +1,46 @@ + + +

{%= __("Balance Sheet") %}

+

{%= filters.fiscal_year %}

+
+ + + + + {% for(var i=2, l=report.columns.length; i{%= report.columns[i].label %} + {% } %} + + + + {% for(var j=0, k=data.length; j + + {% for(var i=2, l=report.columns.length; i + {% var fieldname = report.columns[i].field; %} + {% if (!is_null(row[fieldname])) { %} + {%= format_currency(row[fieldname]) %} + {% } %} + + {% } %} + + {% } %} + +
+ {%= row.account_name %} +
+

Printed On {%= dateutil.str_to_user(dateutil.get_datetime_as_string()) %}

diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 9bdd298077..378a687378 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -1,7 +1,9 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.query_reports["Balance Sheet"] = { +frappe.provide("erpnext.balance_sheet"); + +erpnext.balance_sheet = frappe.query_reports["Balance Sheet"] = { "filters": [ { "fieldname":"company", @@ -26,6 +28,44 @@ frappe.query_reports["Balance Sheet"] = { "options": "Yearly\nQuarterly\nMonthly", "default": "Yearly", "reqd": 1 + }, + { + "fieldname": "depth", + "label": __("Depth"), + "fieldtype": "Select", + "options": "3\n4\n5", + "default": "3" } - ] + ], + "formatter": function(row, cell, value, columnDef, dataContext) { + if (columnDef.df.fieldname=="account") { + var link = $("") + .text(dataContext.account_name) + .attr("onclick", 'erpnext.balance_sheet.open_general_ledger("' + dataContext.account + '")'); + + var span = $("") + .css("padding-left", (cint(dataContext.indent) * 21) + "px") + .append(link); + + value = span.wrap("

").parent().html(); + + } else { + value = frappe.query_reports["Balance Sheet"].default_formatter(row, cell, value, columnDef, dataContext); + } + + if (!dataContext.parent_account) { + value = $(value).css("font-weight", "bold").wrap("

").parent().html(); + } + + return value; + }, + "open_general_ledger": function(account) { + if (!account) return; + + frappe.route_options = { + "account": account, + "company": frappe.query_report.filters_by_name.company.get_value() + }; + frappe.set_route("query-report", "General Ledger"); + } } diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index dc71e92138..3bf424c89b 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -2,40 +2,76 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import babel.dates import frappe -from frappe.utils import cstr, flt +from frappe.utils import (cstr, flt, cint, + getdate, get_first_day, get_last_day, add_months, add_days, now_datetime) from frappe import _ def execute(filters=None): company = filters.company fiscal_year = filters.fiscal_year - depth = 3 - end_date = frappe.db.get_value("Fiscal Year", fiscal_year, "year_end_date") + depth = cint(filters.depth) or 3 + start_date, end_date = frappe.db.get_value("Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]) + period_list = get_period_list(start_date, end_date, filters.get("periodicity") or "Yearly", fiscal_year) - for root_type, balance_must_be in (("Asset", "Debit"), ("Liability", "Credit"), ("Equity", "Credit")): + out = [] + for (root_type, balance_must_be) in (("Asset", "Debit"), ("Liability", "Credit"), ("Equity", "Credit")): + data = [] accounts, account_gl_entries = get_accounts_and_gl_entries(root_type, company, end_date) if accounts: accounts, accounts_map = filter_accounts(accounts, depth=depth) for d in accounts: - d.debit = d.credit = 0 for account_name in ([d.name] + (d.invisible_children or [])): for each in account_gl_entries.get(account_name, []): - d.debit += flt(each.debit) - d.credit += flt(each.credit) + for period_start_date, period_end_date, period_key, period_label in period_list: + each.posting_date = getdate(each.posting_date) + + # check if posting date is within the period + if ((not period_start_date or (each.posting_date >= period_start_date)) + and (each.posting_date <= period_end_date)): + + d[period_key] = d.get(period_key, 0.0) + flt(each.debit) - flt(each.credit) for d in reversed(accounts): if d.parent_account: - accounts_map[d.parent_account]["debit"] += d.debit - accounts_map[d.parent_account]["credit"] += d.credit + for period_start_date, period_end_date, period_key, period_label in period_list: + accounts_map[d.parent_account][period_key] = accounts_map[d.parent_account].get(period_key, 0.0) + d.get(period_key, 0.0) - for d in accounts: - d.balance = d["debit"] - d["credit"] - if d.balance: - d.balance *= (1 if balance_must_be=="Debit" else -1) - print (" " * d["indent"] * 2) + d["account_name"], d["balance"], balance_must_be + for i, d in enumerate(accounts): + has_value = False + row = {"account_name": d["account_name"], "account": d["name"], "indent": d["indent"], "parent_account": d["parent_account"]} + for period_start_date, period_end_date, period_key, period_label in period_list: + if d.get(period_key): + d[period_key] *= (1 if balance_must_be=="Debit" else -1) - return [], [] + row[period_key] = d.get(period_key, 0.0) + if row[period_key]: + has_value = True + + if has_value: + data.append(row) + + if data: + row = {"account_name": _("Total ({0})").format(balance_must_be), "account": None} + for period_start_date, period_end_date, period_key, period_label in period_list: + if period_key in data[0]: + row[period_key] = data[0].get(period_key, 0.0) + data[0][period_key] = "" + + data.append(row) + + # blank row after Total + data.append({}) + + out.extend(data) + + columns = [{"fieldname": "account", "label": _("Account"), "fieldtype": "Link", "options": "Account", "width": 300}] + for period_start_date, period_end_date, period_key, period_label in period_list: + columns.append({"fieldname": period_key, "label": period_label, "fieldtype": "Currency", "width": 150}) + + return columns, out def get_accounts_and_gl_entries(root_type, company, end_date): # root lft, rgt @@ -97,3 +133,57 @@ def filter_accounts(accounts, depth): add_to_data(None, 0) return data, accounts_map + +def get_period_list(start_date, end_date, periodicity, fiscal_year): + """Get a list of tuples that represents (period_start_date, period_end_date, period_key) + Periodicity can be (Yearly, Quarterly, Monthly)""" + + start_date = getdate(start_date) + end_date = getdate(end_date) + today = now_datetime().date() + + if periodicity == "Yearly": + period_list = [(None, end_date, fiscal_year, fiscal_year)] + else: + months_to_add = { + "Half-yearly": 6, + "Quarterly": 3, + "Monthly": 1 + }[periodicity] + + period_list = [] + + # start with first day, so as to avoid year start dates like 2-April if every they occur + next_date = get_first_day(start_date) + + for i in xrange(12 / months_to_add): + next_date = add_months(next_date, months_to_add) + + if next_date == get_first_day(next_date): + # if first day, get the last day of previous month + next_date = add_days(next_date, -1) + else: + # get the last day of the month + next_date = get_last_day(next_date) + + # checking in the middle of the fiscal year? don't show future periods + if next_date > today: + break + + elif next_date <= end_date: + key = next_date.strftime("%b_%Y").lower() + label = babel.dates.format_date(next_date, "MMM YYYY", locale=(frappe.local.lang or "").replace("-", "_")) + period_list.append((None, next_date, key, label)) + + # if it ends before a full year + if next_date == end_date: + break + + else: + # if it ends before a full year + key = end_date.strftime("%b_%Y").lower() + label = babel.dates.format_date(end_date, "MMM YYYY", locale=(frappe.local.lang or "").replace("-", "_")) + period_list.append((None, end_date, key, label)) + break + + return period_list diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 2d757f1ef5..0e3e2d4ae5 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -195,6 +195,12 @@ def get_data(): "doctype": "Purchase Invoice", "is_query_report": True }, + { + "type": "report", + "name": "Balance Sheet", + "doctype": "GL Entry", + "is_query_report": True + }, { "type": "page", "name": "financial-analytics", From 825d01461663a486293f9db869272d47614e0ea8 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 18 Jul 2014 18:05:26 +0530 Subject: [PATCH 3/7] Commonfied code for Financial Statements and added Profit and Loss Statement --- .../report/balance_sheet/balance_sheet.js | 69 +---- .../report/balance_sheet/balance_sheet.py | 186 +------------ ...e_sheet.html => financial_statements.html} | 21 +- .../accounts/report/financial_statements.py | 252 ++++++++++++++++++ .../profit_and_loss_statement/__init__.py | 0 .../profit_and_loss_statement.js | 6 + .../profit_and_loss_statement.json | 17 ++ .../profit_and_loss_statement.py | 42 +++ erpnext/config/accounts.py | 6 + erpnext/public/js/financial_statements.js | 68 +++++ 10 files changed, 417 insertions(+), 250 deletions(-) rename erpnext/accounts/report/{balance_sheet/balance_sheet.html => financial_statements.html} (56%) create mode 100644 erpnext/accounts/report/financial_statements.py create mode 100644 erpnext/accounts/report/profit_and_loss_statement/__init__.py create mode 100644 erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js create mode 100644 erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.json create mode 100644 erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py create mode 100644 erpnext/public/js/financial_statements.js diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 378a687378..a28008e9b6 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -1,71 +1,6 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.provide("erpnext.balance_sheet"); +frappe.require("assets/erpnext/js/financial_statements.js"); -erpnext.balance_sheet = frappe.query_reports["Balance Sheet"] = { - "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 - }, - { - "fieldname": "periodicity", - "label": __("Periodicity"), - "fieldtype": "Select", - "options": "Yearly\nQuarterly\nMonthly", - "default": "Yearly", - "reqd": 1 - }, - { - "fieldname": "depth", - "label": __("Depth"), - "fieldtype": "Select", - "options": "3\n4\n5", - "default": "3" - } - ], - "formatter": function(row, cell, value, columnDef, dataContext) { - if (columnDef.df.fieldname=="account") { - var link = $("") - .text(dataContext.account_name) - .attr("onclick", 'erpnext.balance_sheet.open_general_ledger("' + dataContext.account + '")'); - - var span = $("") - .css("padding-left", (cint(dataContext.indent) * 21) + "px") - .append(link); - - value = span.wrap("

").parent().html(); - - } else { - value = frappe.query_reports["Balance Sheet"].default_formatter(row, cell, value, columnDef, dataContext); - } - - if (!dataContext.parent_account) { - value = $(value).css("font-weight", "bold").wrap("

").parent().html(); - } - - return value; - }, - "open_general_ledger": function(account) { - if (!account) return; - - frappe.route_options = { - "account": account, - "company": frappe.query_report.filters_by_name.company.get_value() - }; - frappe.set_route("query-report", "General Ledger"); - } -} +frappe.query_reports["Balance Sheet"] = erpnext.financial_statements; diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 3bf424c89b..dd6abfd01c 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -2,188 +2,22 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import babel.dates import frappe -from frappe.utils import (cstr, flt, cint, - getdate, get_first_day, get_last_day, add_months, add_days, now_datetime) -from frappe import _ +from erpnext.accounts.report.financial_statements import (process_filters, get_period_list, get_columns, get_data) + +print_path = "accounts/report/financial_statements.html" def execute(filters=None): - company = filters.company - fiscal_year = filters.fiscal_year - depth = cint(filters.depth) or 3 - start_date, end_date = frappe.db.get_value("Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]) - period_list = get_period_list(start_date, end_date, filters.get("periodicity") or "Yearly", fiscal_year) - - out = [] - for (root_type, balance_must_be) in (("Asset", "Debit"), ("Liability", "Credit"), ("Equity", "Credit")): - data = [] - accounts, account_gl_entries = get_accounts_and_gl_entries(root_type, company, end_date) - if accounts: - accounts, accounts_map = filter_accounts(accounts, depth=depth) - - for d in accounts: - for account_name in ([d.name] + (d.invisible_children or [])): - for each in account_gl_entries.get(account_name, []): - for period_start_date, period_end_date, period_key, period_label in period_list: - each.posting_date = getdate(each.posting_date) - - # check if posting date is within the period - if ((not period_start_date or (each.posting_date >= period_start_date)) - and (each.posting_date <= period_end_date)): - - d[period_key] = d.get(period_key, 0.0) + flt(each.debit) - flt(each.credit) - - for d in reversed(accounts): - if d.parent_account: - for period_start_date, period_end_date, period_key, period_label in period_list: - accounts_map[d.parent_account][period_key] = accounts_map[d.parent_account].get(period_key, 0.0) + d.get(period_key, 0.0) - - for i, d in enumerate(accounts): - has_value = False - row = {"account_name": d["account_name"], "account": d["name"], "indent": d["indent"], "parent_account": d["parent_account"]} - for period_start_date, period_end_date, period_key, period_label in period_list: - if d.get(period_key): - d[period_key] *= (1 if balance_must_be=="Debit" else -1) - - row[period_key] = d.get(period_key, 0.0) - if row[period_key]: - has_value = True - - if has_value: - data.append(row) - - if data: - row = {"account_name": _("Total ({0})").format(balance_must_be), "account": None} - for period_start_date, period_end_date, period_key, period_label in period_list: - if period_key in data[0]: - row[period_key] = data[0].get(period_key, 0.0) - data[0][period_key] = "" - - data.append(row) - - # blank row after Total - data.append({}) - - out.extend(data) - - columns = [{"fieldname": "account", "label": _("Account"), "fieldtype": "Link", "options": "Account", "width": 300}] - for period_start_date, period_end_date, period_key, period_label in period_list: - columns.append({"fieldname": period_key, "label": period_label, "fieldtype": "Currency", "width": 150}) - - return columns, out - -def get_accounts_and_gl_entries(root_type, company, end_date): - # root lft, rgt - root_account = frappe.db.sql("""select lft, rgt from `tabAccount` - where company=%s and root_type=%s order by lft limit 1""", - (company, root_type), as_dict=True) - - if not root_account: - return None, None - - lft, rgt = root_account[0].lft, root_account[0].rgt - - accounts = frappe.db.sql("""select * from `tabAccount` - where company=%(company)s and lft >= %(lft)s and rgt <= %(rgt)s order by lft""", - { "company": company, "lft": lft, "rgt": rgt }, as_dict=True) - - gl_entries = frappe.db.sql("""select * from `tabGL Entry` - where company=%(company)s - and posting_date <= %(end_date)s - and account in (select name from `tabAccount` - where lft >= %(lft)s and rgt <= %(rgt)s)""", - { - "company": company, - "end_date": end_date, - "lft": lft, - "rgt": rgt - }, - as_dict=True) - - account_gl_entries = {} - for entry in gl_entries: - account_gl_entries.setdefault(entry.account, []).append(entry) - - return accounts, account_gl_entries - -def filter_accounts(accounts, depth): - parent_children_map = {} - accounts_map = {} - for d in accounts: - accounts_map[d.name] = d - parent_children_map.setdefault(d.parent_account or None, []).append(d) + process_filters(filters) + period_list = get_period_list(filters.fiscal_year, filters.periodicity, from_beginning=True) data = [] - def add_to_data(parent, level): - if level < depth: - for child in (parent_children_map.get(parent) or []): - child.indent = level - data.append(child) - add_to_data(child.name, level + 1) + for (root_type, balance_must_be) in (("Asset", "Debit"), ("Liability", "Credit"), ("Equity", "Credit")): + result = get_data(filters.company, root_type, balance_must_be, period_list, filters.depth) + data.extend(result or []) - else: - # include all children at level lower than the depth - parent_account = accounts_map[parent] - parent_account["invisible_children"] = [] - for d in accounts: - if d.lft > parent_account.lft and d.rgt < parent_account.rgt: - parent_account["invisible_children"].append(d.name) + columns = get_columns(period_list) - add_to_data(None, 0) + return columns, data - return data, accounts_map -def get_period_list(start_date, end_date, periodicity, fiscal_year): - """Get a list of tuples that represents (period_start_date, period_end_date, period_key) - Periodicity can be (Yearly, Quarterly, Monthly)""" - - start_date = getdate(start_date) - end_date = getdate(end_date) - today = now_datetime().date() - - if periodicity == "Yearly": - period_list = [(None, end_date, fiscal_year, fiscal_year)] - else: - months_to_add = { - "Half-yearly": 6, - "Quarterly": 3, - "Monthly": 1 - }[periodicity] - - period_list = [] - - # start with first day, so as to avoid year start dates like 2-April if every they occur - next_date = get_first_day(start_date) - - for i in xrange(12 / months_to_add): - next_date = add_months(next_date, months_to_add) - - if next_date == get_first_day(next_date): - # if first day, get the last day of previous month - next_date = add_days(next_date, -1) - else: - # get the last day of the month - next_date = get_last_day(next_date) - - # checking in the middle of the fiscal year? don't show future periods - if next_date > today: - break - - elif next_date <= end_date: - key = next_date.strftime("%b_%Y").lower() - label = babel.dates.format_date(next_date, "MMM YYYY", locale=(frappe.local.lang or "").replace("-", "_")) - period_list.append((None, next_date, key, label)) - - # if it ends before a full year - if next_date == end_date: - break - - else: - # if it ends before a full year - key = end_date.strftime("%b_%Y").lower() - label = babel.dates.format_date(end_date, "MMM YYYY", locale=(frappe.local.lang or "").replace("-", "_")) - period_list.append((None, end_date, key, label)) - break - - return period_list diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.html b/erpnext/accounts/report/financial_statements.html similarity index 56% rename from erpnext/accounts/report/balance_sheet/balance_sheet.html rename to erpnext/accounts/report/financial_statements.html index a6a33f594e..403e67e5bb 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.html +++ b/erpnext/accounts/report/financial_statements.html @@ -1,20 +1,27 @@ +{% + if (report.columns.length > 6) { + frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); + } +%} + -

{%= __("Balance Sheet") %}

+

{%= __(report.report_name) %}

+

{%= filters.company %}

{%= filters.fiscal_year %}


- + {% for(var i=2, l=report.columns.length; i{%= report.columns[i].label %} {% } %} @@ -24,12 +31,12 @@ {% for(var j=0, k=data.length; j {% for(var i=2, l=report.columns.length; i diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py new file mode 100644 index 0000000000..3490146eec --- /dev/null +++ b/erpnext/accounts/report/financial_statements.py @@ -0,0 +1,252 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _, _dict +from frappe.utils import (cstr, flt, cint, + getdate, get_first_day, get_last_day, add_months, add_days, now_datetime, localize_date) + +def process_filters(filters): + filters.depth = cint(filters.depth) or 3 + if not filters.periodicity: + filters.periodicity = "Yearly" + +def get_period_list(fiscal_year, periodicity, from_beginning=False): + """Get a list of dict {"to_date": to_date, "key": key, "label": label} + Periodicity can be (Yearly, Quarterly, Monthly)""" + + start_date, end_date = frappe.db.get_value("Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]) + start_date = getdate(start_date) + end_date = getdate(end_date) + today = now_datetime().date() + + if periodicity == "Yearly": + period_list = [_dict({"to_date": end_date, "key": fiscal_year, "label": fiscal_year})] + else: + months_to_add = { + "Half-yearly": 6, + "Quarterly": 3, + "Monthly": 1 + }[periodicity] + + period_list = [] + + # start with first day, so as to avoid year to_dates like 2-April if ever they occur + to_date = get_first_day(start_date) + + for i in xrange(12 / months_to_add): + to_date = add_months(to_date, months_to_add) + + if to_date == get_first_day(to_date): + # if to_date is the first day, get the last day of previous month + to_date = add_days(to_date, -1) + else: + # to_date should be the last day of the new to_date's month + to_date = get_last_day(to_date) + + if to_date > today: + # checking in the middle of the currenct fiscal year? don't show future periods + key = today.strftime("%b_%Y").lower() + label = localize_date(today, "MMM YYYY") + period_list.append(_dict({"to_date": today, "key": key, "label": label})) + break + + elif to_date <= end_date: + # the normal case + key = to_date.strftime("%b_%Y").lower() + label = localize_date(to_date, "MMM YYYY") + period_list.append(_dict({"to_date": to_date, "key": key, "label": label})) + + # if it ends before a full year + if to_date == end_date: + break + + else: + # if a fiscal year ends before a 12 month period + key = end_date.strftime("%b_%Y").lower() + label = localize_date(end_date, "MMM YYYY") + period_list.append(_dict({"to_date": end_date, "key": key, "label": label})) + break + + # common processing + for opts in period_list: + opts["key"] = opts["key"].replace(" ", "_").replace("-", "_") + + if from_beginning: + # set start date as None for all fiscal periods, used in case of Balance Sheet + opts["from_date"] = None + else: + opts["from_date"] = start_date + + return period_list + +def get_data(company, root_type, balance_must_be, period_list, depth, ignore_opening_and_closing_entries=False): + accounts = get_accounts(company, root_type) + if not accounts: + return None + + accounts, accounts_by_name = filter_accounts(accounts, depth) + gl_entries_by_account = get_gl_entries(company, root_type, period_list[0]["from_date"], period_list[-1]["to_date"], + accounts[0].lft, accounts[0].rgt, ignore_opening_and_closing_entries=ignore_opening_and_closing_entries) + + calculate_values(accounts, gl_entries_by_account, period_list) + accumulate_values_into_parents(accounts, accounts_by_name, period_list) + out = prepare_data(accounts, balance_must_be, period_list) + + if out: + add_total_row(out, balance_must_be, period_list) + + return out + +def calculate_values(accounts, gl_entries_by_account, period_list): + for d in accounts: + for name in ([d.name] + (d.collapsed_children or [])): + for entry in gl_entries_by_account.get(name, []): + for period in period_list: + entry.posting_date = getdate(entry.posting_date) + + # check if posting date is within the period + if entry.posting_date <= period.to_date: + d[period.key] = d.get(period.key, 0.0) + flt(entry.debit) - flt(entry.credit) + + +def accumulate_values_into_parents(accounts, accounts_by_name, period_list): + """accumulate children's values in parent accounts""" + for d in reversed(accounts): + if d.parent_account: + for period in period_list: + accounts_by_name[d.parent_account][period.key] = accounts_by_name[d.parent_account].get(period.key, 0.0) + \ + d.get(period.key, 0.0) + +def prepare_data(accounts, balance_must_be, period_list): + out = [] + for d in accounts: + # add to output + has_value = False + row = { + "account_name": d.account_name, + "account": d.name, + "parent_account": d.parent_account, + "indent": flt(d.indent) + } + for period in period_list: + if d.get(period.key): + # change sign based on Debit or Credit, since calculation is done using (debit - credit) + d[period.key] *= (1 if balance_must_be=="Debit" else -1) + has_value = True + + row[period.key] = flt(d.get(period.key, 0.0), 3) + + if has_value: + out.append(row) + + return out + +def add_total_row(out, balance_must_be, period_list): + row = { + "account_name": _("Total ({0})").format(balance_must_be), + "account": None + } + for period in period_list: + row[period.key] = out[0].get(period.key, 0.0) + out[0][period.key] = "" + + out.append(row) + + # blank row after Total + out.append({}) + +def get_accounts(company, root_type): + # root lft, rgt + root_account = frappe.db.sql("""select lft, rgt from `tabAccount` + where company=%s and root_type=%s order by lft limit 1""", + (company, root_type), as_dict=True) + + if not root_account: + return None + + lft, rgt = root_account[0].lft, root_account[0].rgt + + accounts = frappe.db.sql("""select * from `tabAccount` + where company=%(company)s and lft >= %(lft)s and rgt <= %(rgt)s order by lft""", + { "company": company, "lft": lft, "rgt": rgt }, as_dict=True) + + return accounts + +def filter_accounts(accounts, depth): + parent_children_map = {} + accounts_by_name = {} + for d in accounts: + accounts_by_name[d.name] = d + parent_children_map.setdefault(d.parent_account or None, []).append(d) + + filtered_accounts = [] + def add_to_list(parent, level): + if level < depth: + for child in (parent_children_map.get(parent) or []): + child.indent = level + filtered_accounts.append(child) + add_to_list(child.name, level + 1) + + else: + # include all children at level lower than the depth + parent_account = accounts_by_name[parent] + parent_account["collapsed_children"] = [] + for d in accounts: + if d.lft > parent_account.lft and d.rgt < parent_account.rgt: + parent_account["collapsed_children"].append(d.name) + + add_to_list(None, 0) + + return filtered_accounts, accounts_by_name + +def get_gl_entries(company, root_type, from_date, to_date, root_lft, root_rgt, ignore_opening_and_closing_entries=False): + """Returns a dict like { "account": [gl entries], ... }""" + additional_conditions = [] + + if ignore_opening_and_closing_entries: + additional_conditions.append("and ifnull(is_opening, 'No')='No' 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 * from `tabGL Entry` + where company=%(company)s + {additional_conditions} + 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)), + { + "company": company, + "from_date": from_date, + "to_date": to_date, + "lft": root_lft, + "rgt": root_rgt + }, + as_dict=True) + + gl_entries_by_account = {} + for entry in gl_entries: + gl_entries_by_account.setdefault(entry.account, []).append(entry) + + return gl_entries_by_account + +def get_columns(period_list): + columns = [{ + "fieldname": "account", + "label": _("Account"), + "fieldtype": "Link", + "options": "Account", + "width": 300 + }] + for period in period_list: + columns.append({ + "fieldname": period.key, + "label": period.label, + "fieldtype": "Currency", + "width": 150 + }) + + return columns diff --git a/erpnext/accounts/report/profit_and_loss_statement/__init__.py b/erpnext/accounts/report/profit_and_loss_statement/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..d047fea5fb --- /dev/null +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -0,0 +1,6 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.require("assets/erpnext/js/financial_statements.js"); + +frappe.query_reports["Profit and Loss Statement"] = erpnext.financial_statements; diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.json b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.json new file mode 100644 index 0000000000..a7608d8cae --- /dev/null +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.json @@ -0,0 +1,17 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2014-07-18 11:43:33.173207", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "is_standard": "Yes", + "modified": "2014-07-18 11:43:33.173207", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Profit and Loss Statement", + "owner": "Administrator", + "ref_doctype": "GL Entry", + "report_name": "Profit and Loss Statement", + "report_type": "Script Report" +} \ 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 new file mode 100644 index 0000000000..556883661b --- /dev/null +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -0,0 +1,42 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt +from erpnext.accounts.report.financial_statements import (process_filters, get_period_list, get_columns, get_data) + +print_path = "accounts/report/financial_statements.html" + +def execute(filters=None): + process_filters(filters) + period_list = get_period_list(filters.fiscal_year, filters.periodicity) + + data = [] + income = get_data(filters.company, "Income", "Credit", period_list, filters.depth, + ignore_opening_and_closing_entries=True) + expense = get_data(filters.company, "Expense", "Debit", period_list, filters.depth, + ignore_opening_and_closing_entries=True) + net_total = get_net_total(income, expense, period_list) + + data.extend(income or []) + data.extend(expense or []) + if net_total: + data.append(net_total) + + columns = get_columns(period_list) + + return columns, data + +def get_net_total(income, expense, period_list): + if income and expense: + net_total = { + "account_name": _("Net Profit / Loss"), + "account": None + } + + for period in period_list: + net_total[period.key] = flt(income[-2][period.key] - expense[-2][period.key], 3) + + return net_total diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 0e3e2d4ae5..722bf77f83 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -201,6 +201,12 @@ def get_data(): "doctype": "GL Entry", "is_query_report": True }, + { + "type": "report", + "name": "Profit and Loss Statement", + "doctype": "GL Entry", + "is_query_report": True + }, { "type": "page", "name": "financial-analytics", diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js new file mode 100644 index 0000000000..dbe41ff345 --- /dev/null +++ b/erpnext/public/js/financial_statements.js @@ -0,0 +1,68 @@ +frappe.provide("erpnext.financial_statements"); + +erpnext.financial_statements = { + "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 + }, + { + "fieldname": "periodicity", + "label": __("Periodicity"), + "fieldtype": "Select", + "options": "Yearly\nHalf-yearly\nQuarterly\nMonthly", + "default": "Yearly", + "reqd": 1 + }, + { + "fieldname": "depth", + "label": __("Depth"), + "fieldtype": "Select", + "options": "3\n4\n5", + "default": "3" + } + ], + "formatter": function(row, cell, value, columnDef, dataContext) { + if (columnDef.df.fieldname=="account") { + var link = $("") + .text(dataContext.account_name) + .attr("onclick", 'erpnext.financial_statements.open_general_ledger("' + dataContext.account + '")'); + + var span = $("") + .css("padding-left", (cint(dataContext.indent) * 21) + "px") + .append(link); + + value = span.wrap("

").parent().html(); + + } else { + value = erpnext.financial_statements.default_formatter(row, cell, value, columnDef, dataContext); + } + + if (!dataContext.parent_account) { + value = $(value).css("font-weight", "bold").wrap("

").parent().html(); + } + + return value; + }, + "open_general_ledger": function(account) { + if (!account) return; + + frappe.route_options = { + "account": account, + "company": frappe.query_report.filters_by_name.company.get_value() + }; + frappe.set_route("query-report", "General Ledger"); + } +}; From db4ba39824dd0a188c743aadfdc9404a26a80a31 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 18 Jul 2014 18:20:44 +0530 Subject: [PATCH 4/7] Better handling of ignore zero values --- erpnext/accounts/report/financial_statements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 3490146eec..59b28da779 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -134,10 +134,13 @@ def prepare_data(accounts, balance_must_be, period_list): if d.get(period.key): # change sign based on Debit or Credit, since calculation is done using (debit - credit) d[period.key] *= (1 if balance_must_be=="Debit" else -1) - has_value = True row[period.key] = flt(d.get(period.key, 0.0), 3) + if abs(row[period.key]) >= 0.005: + # ignore zero values + has_value = True + if has_value: out.append(row) From 6cc5babd2e67731a2c1248fb6fcf55c86cf3e237 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 21 Jul 2014 11:43:03 +0530 Subject: [PATCH 5/7] Use include directive to embed common print format --- erpnext/accounts/report/balance_sheet/balance_sheet.html | 1 + erpnext/accounts/report/balance_sheet/balance_sheet.py | 2 -- .../profit_and_loss_statement/profit_and_loss_statement.html | 1 + .../profit_and_loss_statement/profit_and_loss_statement.py | 2 -- 4 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 erpnext/accounts/report/balance_sheet/balance_sheet.html create mode 100644 erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.html diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.html b/erpnext/accounts/report/balance_sheet/balance_sheet.html new file mode 100644 index 0000000000..d4ae54d4f3 --- /dev/null +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.html @@ -0,0 +1 @@ +{% include "accounts/report/financial_statements.html" %} diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index dd6abfd01c..425257ffb5 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -5,8 +5,6 @@ from __future__ import unicode_literals import frappe from erpnext.accounts.report.financial_statements import (process_filters, get_period_list, get_columns, get_data) -print_path = "accounts/report/financial_statements.html" - def execute(filters=None): process_filters(filters) period_list = get_period_list(filters.fiscal_year, filters.periodicity, from_beginning=True) diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.html b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.html new file mode 100644 index 0000000000..d4ae54d4f3 --- /dev/null +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.html @@ -0,0 +1 @@ +{% include "accounts/report/financial_statements.html" %} 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 556883661b..9886f2f497 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 @@ -7,8 +7,6 @@ from frappe import _ from frappe.utils import flt from erpnext.accounts.report.financial_statements import (process_filters, get_period_list, get_columns, get_data) -print_path = "accounts/report/financial_statements.html" - def execute(filters=None): process_filters(filters) period_list = get_period_list(filters.fiscal_year, filters.periodicity) From f457bce0e7a97110d2d82d7bba984449a654fc9d Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 21 Jul 2014 12:03:04 +0530 Subject: [PATCH 6/7] Changed localize_date to formatdate --- erpnext/accounts/report/financial_statements.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 59b28da779..30650fff7d 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -4,8 +4,8 @@ from __future__ import unicode_literals import frappe from frappe import _, _dict -from frappe.utils import (cstr, flt, cint, - getdate, get_first_day, get_last_day, add_months, add_days, now_datetime, localize_date) +from frappe.utils import (flt, cint, getdate, get_first_day, get_last_day, + add_months, add_days, now_datetime, formatdate) def process_filters(filters): filters.depth = cint(filters.depth) or 3 @@ -48,14 +48,14 @@ def get_period_list(fiscal_year, periodicity, from_beginning=False): if to_date > today: # checking in the middle of the currenct fiscal year? don't show future periods key = today.strftime("%b_%Y").lower() - label = localize_date(today, "MMM YYYY") + label = formatdate(today, "MMM YYYY") period_list.append(_dict({"to_date": today, "key": key, "label": label})) break elif to_date <= end_date: # the normal case key = to_date.strftime("%b_%Y").lower() - label = localize_date(to_date, "MMM YYYY") + label = formatdate(to_date, "MMM YYYY") period_list.append(_dict({"to_date": to_date, "key": key, "label": label})) # if it ends before a full year @@ -65,7 +65,7 @@ def get_period_list(fiscal_year, periodicity, from_beginning=False): else: # if a fiscal year ends before a 12 month period key = end_date.strftime("%b_%Y").lower() - label = localize_date(end_date, "MMM YYYY") + label = formatdate(end_date, "MMM YYYY") period_list.append(_dict({"to_date": end_date, "key": key, "label": label})) break From 5f0459c32176e851f9f01ce3f17417d35d3c26e4 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 21 Jul 2014 16:13:06 +0530 Subject: [PATCH 7/7] Fixes to Financial Statements --- .../report/balance_sheet/balance_sheet.py | 38 +++++++++++++-- .../accounts/report/financial_statements.py | 46 +++++++++---------- .../profit_and_loss_statement.py | 25 +++++----- erpnext/public/js/financial_statements.js | 19 +++++--- 4 files changed, 83 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 425257ffb5..4edc9b9515 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -3,19 +3,51 @@ from __future__ import unicode_literals import frappe +from frappe import _ +from frappe.utils import flt from erpnext.accounts.report.financial_statements import (process_filters, get_period_list, get_columns, get_data) def execute(filters=None): process_filters(filters) period_list = get_period_list(filters.fiscal_year, filters.periodicity, from_beginning=True) + asset = get_data(filters.company, "Asset", "Debit", period_list, filters.depth) + liability = get_data(filters.company, "Liability", "Credit", period_list, filters.depth) + equity = get_data(filters.company, "Equity", "Credit", period_list, filters.depth) + provisional_profit_loss = get_provisional_profit_loss(asset, liability, equity, period_list) + data = [] - for (root_type, balance_must_be) in (("Asset", "Debit"), ("Liability", "Credit"), ("Equity", "Credit")): - result = get_data(filters.company, root_type, balance_must_be, period_list, filters.depth) - data.extend(result or []) + data.extend(asset or []) + data.extend(liability or []) + data.extend(equity or []) + if provisional_profit_loss: + data.append(provisional_profit_loss) columns = get_columns(period_list) return columns, data +def get_provisional_profit_loss(asset, liability, equity, period_list): + if asset and (liability or equity): + provisional_profit_loss = { + "account_name": _("Provisional Profit / Loss (Credit)"), + "account": None, + "is_profit_loss": True + } + has_value = False + + for period in period_list: + effective_liability = 0.0 + if liability: + effective_liability += flt(liability[-2][period.key]) + if equity: + effective_liability += flt(equity[-2][period.key]) + + provisional_profit_loss[period.key] = flt(asset[-2][period.key]) - effective_liability + + if provisional_profit_loss[period.key]: + has_value = True + + if has_value: + return provisional_profit_loss diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 30650fff7d..45d5b3a737 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _, _dict from frappe.utils import (flt, cint, getdate, get_first_day, get_last_day, - add_months, add_days, now_datetime, formatdate) + add_months, add_days, formatdate) def process_filters(filters): filters.depth = cint(filters.depth) or 3 @@ -19,7 +19,6 @@ def get_period_list(fiscal_year, periodicity, from_beginning=False): start_date, end_date = frappe.db.get_value("Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]) start_date = getdate(start_date) end_date = getdate(end_date) - today = now_datetime().date() if periodicity == "Yearly": period_list = [_dict({"to_date": end_date, "key": fiscal_year, "label": fiscal_year})] @@ -45,18 +44,9 @@ def get_period_list(fiscal_year, periodicity, from_beginning=False): # to_date should be the last day of the new to_date's month to_date = get_last_day(to_date) - if to_date > today: - # checking in the middle of the currenct fiscal year? don't show future periods - key = today.strftime("%b_%Y").lower() - label = formatdate(today, "MMM YYYY") - period_list.append(_dict({"to_date": today, "key": key, "label": label})) - break - - elif to_date <= end_date: + if to_date <= end_date: # the normal case - key = to_date.strftime("%b_%Y").lower() - label = formatdate(to_date, "MMM YYYY") - period_list.append(_dict({"to_date": to_date, "key": key, "label": label})) + period_list.append(_dict({ "to_date": to_date })) # if it ends before a full year if to_date == end_date: @@ -64,14 +54,19 @@ def get_period_list(fiscal_year, periodicity, from_beginning=False): else: # if a fiscal year ends before a 12 month period - key = end_date.strftime("%b_%Y").lower() - label = formatdate(end_date, "MMM YYYY") - period_list.append(_dict({"to_date": end_date, "key": key, "label": label})) + period_list.append(_dict({ "to_date": end_date })) break # common processing for opts in period_list: - opts["key"] = opts["key"].replace(" ", "_").replace("-", "_") + key = opts["to_date"].strftime("%b_%Y").lower() + label = formatdate(opts["to_date"], "MMM YYYY") + opts.update({ + "key": key.replace(" ", "_").replace("-", "_"), + "label": label, + "year_start_date": start_date, + "year_end_date": end_date + }) if from_beginning: # set start date as None for all fiscal periods, used in case of Balance Sheet @@ -81,14 +76,14 @@ def get_period_list(fiscal_year, periodicity, from_beginning=False): return period_list -def get_data(company, root_type, balance_must_be, period_list, depth, ignore_opening_and_closing_entries=False): +def get_data(company, root_type, balance_must_be, period_list, depth, ignore_closing_entries=False): accounts = get_accounts(company, root_type) if not accounts: return None accounts, accounts_by_name = filter_accounts(accounts, depth) gl_entries_by_account = get_gl_entries(company, root_type, period_list[0]["from_date"], period_list[-1]["to_date"], - accounts[0].lft, accounts[0].rgt, ignore_opening_and_closing_entries=ignore_opening_and_closing_entries) + accounts[0].lft, accounts[0].rgt, ignore_closing_entries=ignore_closing_entries) calculate_values(accounts, gl_entries_by_account, period_list) accumulate_values_into_parents(accounts, accounts_by_name, period_list) @@ -121,6 +116,9 @@ def accumulate_values_into_parents(accounts, accounts_by_name, period_list): def prepare_data(accounts, balance_must_be, period_list): out = [] + year_start_date = period_list[0]["year_start_date"].strftime("%Y-%m-%d") + year_end_date = period_list[-1]["year_end_date"].strftime("%Y-%m-%d") + for d in accounts: # add to output has_value = False @@ -128,7 +126,9 @@ def prepare_data(accounts, balance_must_be, period_list): "account_name": d.account_name, "account": d.name, "parent_account": d.parent_account, - "indent": flt(d.indent) + "indent": flt(d.indent), + "year_start_date": year_start_date, + "year_end_date": year_end_date } for period in period_list: if d.get(period.key): @@ -204,12 +204,12 @@ def filter_accounts(accounts, depth): return filtered_accounts, accounts_by_name -def get_gl_entries(company, root_type, from_date, to_date, root_lft, root_rgt, ignore_opening_and_closing_entries=False): +def get_gl_entries(company, root_type, from_date, to_date, root_lft, root_rgt, ignore_closing_entries=False): """Returns a dict like { "account": [gl entries], ... }""" additional_conditions = [] - if ignore_opening_and_closing_entries: - additional_conditions.append("and ifnull(is_opening, 'No')='No' and ifnull(voucher_type, '')!='Period Closing Voucher'") + 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") 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 9886f2f497..3a3123fc36 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 @@ -11,30 +11,29 @@ def execute(filters=None): process_filters(filters) period_list = get_period_list(filters.fiscal_year, filters.periodicity) - data = [] - income = get_data(filters.company, "Income", "Credit", period_list, filters.depth, - ignore_opening_and_closing_entries=True) - expense = get_data(filters.company, "Expense", "Debit", period_list, filters.depth, - ignore_opening_and_closing_entries=True) - net_total = get_net_total(income, expense, period_list) + income = get_data(filters.company, "Income", "Credit", period_list, filters.depth, ignore_closing_entries=True) + expense = get_data(filters.company, "Expense", "Debit", period_list, filters.depth, ignore_closing_entries=True) + net_profit_loss = get_net_profit_loss(income, expense, period_list) + data = [] data.extend(income or []) data.extend(expense or []) - if net_total: - data.append(net_total) + if net_profit_loss: + data.append(net_profit_loss) columns = get_columns(period_list) return columns, data -def get_net_total(income, expense, period_list): +def get_net_profit_loss(income, expense, period_list): if income and expense: - net_total = { + net_profit_loss = { "account_name": _("Net Profit / Loss"), - "account": None + "account": None, + "is_profit_loss": True } for period in period_list: - net_total[period.key] = flt(income[-2][period.key] - expense[-2][period.key], 3) + net_profit_loss[period.key] = flt(income[-2][period.key] - expense[-2][period.key], 3) - return net_total + return net_profit_loss diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index dbe41ff345..5e3ba0e116 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -38,7 +38,7 @@ erpnext.financial_statements = { if (columnDef.df.fieldname=="account") { var link = $("") .text(dataContext.account_name) - .attr("onclick", 'erpnext.financial_statements.open_general_ledger("' + dataContext.account + '")'); + .attr("onclick", "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(dataContext) + ")"); var span = $("") .css("padding-left", (cint(dataContext.indent) * 21) + "px") @@ -51,17 +51,24 @@ erpnext.financial_statements = { } if (!dataContext.parent_account) { - value = $(value).css("font-weight", "bold").wrap("

").parent().html(); + var $value = $(value).css("font-weight", "bold"); + if (dataContext.is_profit_loss && dataContext[columnDef.df.fieldname] < 0) { + $value.addClass("text-danger"); + } + + value = $value.wrap("

").parent().html(); } return value; }, - "open_general_ledger": function(account) { - if (!account) return; + "open_general_ledger": function(data) { + if (!data.account) return; frappe.route_options = { - "account": account, - "company": frappe.query_report.filters_by_name.company.get_value() + "account": data.account, + "company": frappe.query_report.filters_by_name.company.get_value(), + "from_date": data.year_start_date, + "to_date": data.year_end_date }; frappe.set_route("query-report", "General Ledger"); }
- {%= row.account_name %} + {%= row.account_name %}