Merge pull request #27863 from rohitwaghchaure/multi-currency-issue-for-consolidated-report

fix: consolidated report not consider company currency
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index f6198eb..605262f 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -8,6 +8,8 @@
 from frappe.utils import cint, cstr
 from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
 
+import erpnext
+
 
 class RootNotEditable(frappe.ValidationError): pass
 class BalanceMismatchError(frappe.ValidationError): pass
@@ -196,7 +198,7 @@
 					"company": company,
 					# parent account's currency should be passed down to child account's curreny
 					# if it is None, it picks it up from default company currency, which might be unintended
-					"account_currency": self.account_currency,
+					"account_currency": erpnext.get_company_currency(company),
 					"parent_account": parent_acc_name_map[company]
 				})
 
@@ -207,8 +209,7 @@
 				# update the parent company's value in child companies
 				doc = frappe.get_doc("Account", child_account)
 				parent_value_changed = False
-				for field in ['account_type', 'account_currency',
-					'freeze_account', 'balance_must_be']:
+				for field in ['account_type', 'freeze_account', 'balance_must_be']:
 					if doc.get(field) != self.get(field):
 						parent_value_changed = True
 						doc.set(field, self.get(field))
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index 6a8301a..e24a5f9 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -103,8 +103,11 @@
 				column.is_tree = true;
 			}
 
-			value = default_formatter(value, row, column, data);
+			if (data && data.account && column.apply_currency_formatter) {
+				data.currency = erpnext.get_currency(column.company_name);
+			}
 
+			value = default_formatter(value, row, column, data);
 			if (!data.parent_account) {
 				value = $(`<span>${value}</span>`);
 
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index b0cfbac..e093f96 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -7,8 +7,9 @@
 from frappe import _
 from frappe.utils import cint, flt, getdate
 
+import erpnext
+from collections import defaultdict
 from erpnext.accounts.report.balance_sheet.balance_sheet import (
-	check_opening_balance,
 	get_chart_data,
 	get_provisional_profit_loss,
 )
@@ -31,7 +32,7 @@
 from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
 	get_report_summary as get_pl_summary,
 )
-from erpnext.accounts.report.utils import convert_to_presentation_currency
+from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
 
 
 def execute(filters=None):
@@ -42,7 +43,7 @@
 
 	fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year'))
 	companies_column, companies = get_companies(filters)
-	columns = get_columns(companies_column)
+	columns = get_columns(companies_column, filters)
 
 	if filters.get('report') == "Balance Sheet":
 		data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters)
@@ -73,21 +74,24 @@
 	provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity,
 		companies, filters.get('company'), company_currency, True)
 
-	message, opening_balance = check_opening_balance(asset, liability, equity)
+	message, opening_balance = prepare_companywise_opening_balance(asset, liability, equity, companies)
 
-	if opening_balance and round(opening_balance,2) !=0:
-		unclosed ={
+	if opening_balance:
+		unclosed = {
 			"account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
 			"account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
 			"warn_if_negative": True,
 			"currency": company_currency
 		}
-		for company in companies:
-			unclosed[company] = opening_balance
-			if provisional_profit_loss:
-				provisional_profit_loss[company] = provisional_profit_loss[company] - opening_balance
 
-		unclosed["total"]=opening_balance
+		for company in companies:
+			unclosed[company] = opening_balance.get(company)
+			if provisional_profit_loss and provisional_profit_loss.get(company):
+				provisional_profit_loss[company] = (
+					flt(provisional_profit_loss[company]) - flt(opening_balance.get(company))
+				)
+
+		unclosed["total"] = opening_balance.get(company)
 		data.append(unclosed)
 
 	if provisional_profit_loss:
@@ -102,6 +106,37 @@
 
 	return data, message, chart, report_summary
 
+def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, companies):
+	opening_balance = {}
+	for company in companies:
+		opening_value = 0
+
+		# opening_value = Aseet - liability - equity
+		for data in [asset_data, liability_data, equity_data]:
+			account_name = get_root_account_name(data[0].root_type, company)
+			opening_value += get_opening_balance(account_name, data, company)
+
+		opening_balance[company] = opening_value
+
+	if opening_balance:
+		return _("Previous Financial Year is not closed"), opening_balance
+
+	return '', {}
+
+def get_opening_balance(account_name, data, company):
+	for row in data:
+		if row.get('account_name') == account_name:
+			return row.get('company_wise_opening_bal', {}).get(company, 0.0)
+
+def get_root_account_name(root_type, company):
+	return frappe.get_all(
+		'Account',
+		fields=['account_name'],
+		filters = {'root_type': root_type, 'is_group': 1,
+			'company': company, 'parent_account': ('is', 'not set')},
+		as_list=1
+	)[0][0]
+
 def get_profit_loss_data(fiscal_year, companies, columns, filters):
 	income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
 	company_currency = get_company_currency(filters)
@@ -193,30 +228,37 @@
 	data["total"] = total
 	return data
 
-def get_columns(companies):
-	columns = [{
-		"fieldname": "account",
-		"label": _("Account"),
-		"fieldtype": "Link",
-		"options": "Account",
-		"width": 300
-	}]
-
-	columns.append({
-		"fieldname": "currency",
-		"label": _("Currency"),
-		"fieldtype": "Link",
-		"options": "Currency",
-		"hidden": 1
-	})
+def get_columns(companies, filters):
+	columns = [
+		{
+			"fieldname": "account",
+			"label": _("Account"),
+			"fieldtype": "Link",
+			"options": "Account",
+			"width": 300
+		}, {
+			"fieldname": "currency",
+			"label": _("Currency"),
+			"fieldtype": "Link",
+			"options": "Currency",
+			"hidden": 1
+		}
+	]
 
 	for company in companies:
+		apply_currency_formatter = 1 if not filters.presentation_currency else 0
+		currency = filters.presentation_currency
+		if not currency:
+			currency = erpnext.get_company_currency(company)
+
 		columns.append({
 			"fieldname": company,
-			"label": company,
+			"label": f'{company} ({currency})',
 			"fieldtype": "Currency",
 			"options": "currency",
-			"width": 150
+			"width": 150,
+			"apply_currency_formatter": apply_currency_formatter,
+			"company_name": company
 		})
 
 	return columns
@@ -236,6 +278,8 @@
 		start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
 		end_date = filters.period_end_date
 
+	filters.end_date = end_date
+
 	gl_entries_by_account = {}
 	for root in frappe.db.sql("""select lft, rgt from tabAccount
 			where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
@@ -244,9 +288,10 @@
 			end_date, root.lft, root.rgt, filters,
 			gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False)
 
-	calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
+	calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year)
 	accumulate_values_into_parents(accounts, accounts_by_name, companies)
-	out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency)
+
+	out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
 
 	if out:
 		add_total_row(out, root_type, balance_must_be, companies, company_currency)
@@ -257,7 +302,10 @@
 	return (filters.get('presentation_currency')
 		or frappe.get_cached_value('Company',  filters.company,  "default_currency"))
 
-def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
+def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year):
+	start_date = (fiscal_year.year_start_date
+		if filters.filter_based_on == 'Fiscal Year' else filters.period_start_date)
+
 	for entries in gl_entries_by_account.values():
 		for entry in entries:
 			if entry.account_number:
@@ -266,15 +314,32 @@
 				account_name =  entry.account_name
 
 			d = accounts_by_name.get(account_name)
+
 			if d:
+				debit, credit = 0, 0
 				for company in companies:
 					# check if posting date is within the period
 					if (entry.company == company or (filters.get('accumulated_in_group_company'))
 						and entry.company in companies.get(company)):
-						d[company] = d.get(company, 0.0) + flt(entry.debit) - flt(entry.credit)
+						parent_company_currency = erpnext.get_company_currency(d.company)
+						child_company_currency = erpnext.get_company_currency(entry.company)
+
+						debit, credit = flt(entry.debit), flt(entry.credit)
+
+						if (not filters.get('presentation_currency')
+							and entry.company != company
+							and parent_company_currency != child_company_currency
+							and filters.get('accumulated_in_group_company')):
+							debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date)
+							credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date)
+
+						d[company] = d.get(company, 0.0) + flt(debit) - flt(credit)
+
+						if entry.posting_date < getdate(start_date):
+							d['company_wise_opening_bal'][company] += (flt(debit) - flt(credit))
 
 				if entry.posting_date < getdate(start_date):
-					d["opening_balance"] = d.get("opening_balance", 0.0) + flt(entry.debit) - flt(entry.credit)
+					d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit)
 
 def accumulate_values_into_parents(accounts, accounts_by_name, companies):
 	"""accumulate children's values in parent accounts"""
@@ -282,17 +347,18 @@
 		if d.parent_account:
 			account = d.parent_account_name
 
-			if not accounts_by_name.get(account):
-				continue
+			# if not accounts_by_name.get(account):
+			# 	continue
 
 			for company in companies:
 				accounts_by_name[account][company] = \
 					accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0)
 
+				accounts_by_name[account]['company_wise_opening_bal'][company] += d.get('company_wise_opening_bal', {}).get(company, 0.0)
+
 			accounts_by_name[account]["opening_balance"] = \
 				accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
 
-
 def get_account_heads(root_type, companies, filters):
 	accounts = get_accounts(root_type, filters)
 
@@ -353,7 +419,7 @@
 			`tabAccount` where company = %s and root_type = %s
 		""" , (filters.get('company'), root_type), as_dict=1)
 
-def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency):
+def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters):
 	data = []
 
 	for d in accounts:
@@ -367,10 +433,13 @@
 			"parent_account": _(d.parent_account),
 			"indent": flt(d.indent),
 			"year_start_date": start_date,
+			"root_type": d.root_type,
 			"year_end_date": end_date,
-			"currency": company_currency,
+			"currency": filters.presentation_currency,
+			"company_wise_opening_bal": d.company_wise_opening_bal,
 			"opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1)
 		})
+
 		for company in companies:
 			if d.get(company) and balance_must_be == "Credit":
 				# change sign based on Debit or Credit, since calculation is done using (debit - credit)
@@ -385,6 +454,7 @@
 
 		row["has_value"] = has_value
 		row["total"] = total
+
 		data.append(row)
 
 	return data
@@ -447,6 +517,7 @@
 		'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
 
 def validate_entries(key, entry, accounts_by_name, accounts):
+	# If an account present in the child company and not in the parent company
 	if key not in accounts_by_name:
 		args = get_account_details(entry.account)
 
@@ -456,12 +527,23 @@
 			args.update({
 				'lft': parent_args.lft + 1,
 				'rgt': parent_args.rgt - 1,
+				'indent': 3,
 				'root_type': parent_args.root_type,
-				'report_type': parent_args.report_type
+				'report_type': parent_args.report_type,
+				'parent_account_name': parent_args.account_name,
+				'company_wise_opening_bal': defaultdict(float)
 			})
 
 		accounts_by_name.setdefault(key, args)
-		accounts.append(args)
+
+		idx = len(accounts)
+		# To identify parent account index
+		for index, row in enumerate(accounts):
+			if row.parent_account_name == args.parent_account_name:
+				idx = index
+				break
+
+		accounts.insert(idx+1, args)
 
 def get_additional_conditions(from_date, ignore_closing_entries, filters):
 	additional_conditions = []
@@ -491,7 +573,6 @@
 			for company in companies:
 				total_row.setdefault(company, 0.0)
 				total_row[company] += row.get(company, 0.0)
-				row[company] = 0.0
 
 			total_row.setdefault("total", 0.0)
 			total_row["total"] += flt(row["total"])
@@ -511,6 +592,7 @@
 			account_name = d.account_number + ' - ' + d.account_name
 		else:
 			account_name =  d.account_name
+		d['company_wise_opening_bal'] = defaultdict(float)
 		accounts_by_name[account_name] = d
 
 		parent_children_map.setdefault(d.parent_account or None, []).append(d)