Merge branch 'develop' into dimension-wise-accounts-balance-reports
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
new file mode 100644
index 0000000..6a03948
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
@@ -0,0 +1,81 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.require("assets/erpnext/js/financial_statements.js", function() {
+	frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
+		"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);
+						frappe.query_report.set_filter_value({
+							from_date: fy.year_start_date,
+							to_date: fy.year_end_date
+						});
+					});
+				}
+			},
+			{
+				"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"),
+			},
+			{
+				"fieldname": "finance_book",
+				"label": __("Finance Book"),
+				"fieldtype": "Link",
+				"options": "Finance Book",
+			},
+			{
+				"fieldname": "dimension",
+				"label": __("Select Dimension"),
+				"fieldtype": "Select",
+				"options": get_accounting_dimension_options(),
+				"reqd": 1,
+			},
+		],
+		"formatter": erpnext.financial_statements.formatter,
+		"tree": true,
+		"name_field": "account",
+		"parent_field": "parent_account",
+		"initial_depth": 3
+	}
+
+});
+
+function get_accounting_dimension_options() {
+	let options =["", "Cost Center", "Project"];
+	frappe.db.get_list('Accounting Dimension',
+		{fields:['document_type']}).then((res) => {
+			res.forEach((dimension) => {
+				options.push(dimension.document_type);
+			});
+		});
+	return options
+}
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json
new file mode 100644
index 0000000..6141944
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json
@@ -0,0 +1,22 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-04-09 16:48:59.548018",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-09 16:48:59.548018",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dimension-wise Accounts Balance Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "Dimension-wise Accounts Balance Report",
+ "report_type": "Script Report",
+ "roles": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
new file mode 100644
index 0000000..7f38770
--- /dev/null
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
@@ -0,0 +1,203 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, erpnext
+from frappe import _
+from frappe.utils import (flt, 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
+
+from six import itervalues
+
+def execute(filters=None):
+	validate_filters(filters)
+	dimension_items_list = get_dimension_items_list(filters.dimension, filters.company)
+
+	if not dimension_items_list:
+		return [], []
+
+	dimension_items_list = [''.join(d) for d in dimension_items_list]
+	columns = get_columns(dimension_items_list)
+	data = get_data(filters, dimension_items_list)
+
+	return columns, data
+
+def get_data(filters, dimension_items_list):
+	company_currency = erpnext.get_company_currency(filters.company)
+	acc = frappe.db.sql("""
+		select
+			name, account_number, parent_account, lft, rgt, root_type,
+			report_type, account_name, include_in_gross, account_type, is_group
+		from
+			`tabAccount`
+		where
+			company=%s
+			order by lft""", (filters.company), as_dict=True)
+
+	if not acc:
+		return None
+
+	accounts, accounts_by_name, parent_children_map = filter_accounts(acc)
+
+	min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount`
+		where company=%s""", (filters.company))[0]
+
+	account = frappe.db.sql_list("""select name from `tabAccount`
+		where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company))
+
+	gl_entries_by_account = {}
+	set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account)
+	format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list)
+	accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list)
+	out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list)
+	out = filter_out_zero_value_rows(out, parent_children_map)
+
+	return out
+
+def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account):
+	for item in dimension_items_list:
+		condition = get_condition(filters.from_date, item, filters.dimension)
+		if account:
+			condition += " and account in ({})"\
+				.format(", ".join([frappe.db.escape(d) for d in account]))
+
+		gl_filters = {
+			"company": filters.get("company"),
+			"from_date": filters.get("from_date"),
+			"to_date": filters.get("to_date"),
+			"finance_book": cstr(filters.get("finance_book"))
+		}
+
+		gl_filters['item'] = ''.join(item)
+
+		if filters.get("include_default_book_entries"):
+			gl_filters["company_fb"] = frappe.db.get_value("Company",
+				filters.company, 'default_finance_book')
+
+		for key, value in filters.items():
+			if value:
+				gl_filters.update({
+					key: value
+				})
+
+		gl_entries = frappe.db.sql("""
+		select
+			posting_date, account, debit, credit, is_opening, fiscal_year,
+			debit_in_account_currency, credit_in_account_currency, account_currency
+		from
+			`tabGL Entry`
+		where
+			company=%(company)s
+		{condition}
+		and posting_date <= %(to_date)s
+		and is_cancelled = 0
+		order by account, posting_date""".format(
+			condition=condition),
+			gl_filters, as_dict=True) #nosec
+
+		for entry in gl_entries:
+			entry['dimension_item'] = ''.join(item)
+			gl_entries_by_account.setdefault(entry.account, []).append(entry)
+
+def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list):
+
+	for entries in itervalues(gl_entries_by_account):
+		for entry in entries:
+			d = accounts_by_name.get(entry.account)
+			if not d:
+				frappe.msgprint(
+					_("Could not retrieve information for {0}.").format(entry.account), title="Error",
+					raise_exception=1
+				)
+			for item in dimension_items_list:
+				if item == entry.dimension_item:
+					d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit)
+
+def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list):
+	data = []
+
+	for d in accounts:
+		has_value = False
+		row = {
+			"account": d.name,
+			"parent_account": d.parent_account,
+			"indent": d.indent,
+			"from_date": filters.from_date,
+			"to_date": filters.to_date,
+			"currency": company_currency,
+			"account_name": ('{} - {}'.format(d.account_number, d.account_name)
+				if d.account_number else d.account_name)
+		}
+
+		for item in dimension_items_list:
+			row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3)
+
+			if abs(row[frappe.scrub(item)]) >= 0.005:
+				# ignore zero values
+				has_value = True
+
+		row["has_value"] = has_value
+		data.append(row)
+
+	return data
+
+def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list):
+	"""accumulate children's values in parent accounts"""
+	for d in reversed(accounts):
+		if d.parent_account:
+			for item in dimension_items_list:
+				accounts_by_name[d.parent_account][frappe.scrub(item)] = \
+					accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0)
+
+def get_condition(from_date, item, dimension):
+	conditions = []
+
+	if from_date:
+		conditions.append("posting_date >= %(from_date)s")
+	if dimension:
+		if dimension not in ['Cost Center', 'Project']:
+			if dimension in ['Customer', 'Supplier']:
+				dimension = 'Party'
+			else:
+				dimension = 'Voucher No'
+		txt = "{0} = %(item)s".format(frappe.scrub(dimension))
+		conditions.append(txt)
+
+	return " and {}".format(" and ".join(conditions)) if conditions else ""
+
+def get_dimension_items_list(dimension, company):
+	meta = frappe.get_meta(dimension, cached=False)
+	fieldnames = [d.fieldname for d in meta.get("fields")]
+	filters = {}
+	if 'company' in fieldnames:
+		filters['company'] = company
+	return frappe.get_all(dimension, filters, as_list=True)
+
+def get_columns(dimension_items_list, accumulated_values=1, company=None):
+	columns = [{
+		"fieldname": "account",
+		"label": _("Account"),
+		"fieldtype": "Link",
+		"options": "Account",
+		"width": 300
+	}]
+	if company:
+		columns.append({
+			"fieldname": "currency",
+			"label": _("Currency"),
+			"fieldtype": "Link",
+			"options": "Currency",
+			"hidden": 1
+		})
+	for item in dimension_items_list:
+		columns.append({
+			"fieldname": frappe.scrub(item),
+			"label": item,
+			"fieldtype": "Currency",
+			"options": "currency",
+			"width": 150
+		})
+
+	return columns