Merge pull request #33380 from ruthra-kumar/err_for_invoices_should_reflect_in_ar_ap_report

fix: ERR journals should reported in AR/AP
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a195c57..9d96843 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -99,6 +99,9 @@
 		# Get return entries
 		self.get_return_entries()
 
+		# Get Exchange Rate Revaluations
+		self.get_exchange_rate_revaluations()
+
 		self.data = []
 
 		for ple in self.ple_entries:
@@ -251,7 +254,8 @@
 			row.invoice_grand_total = row.invoiced
 
 			if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
-				abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
+				(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
+				or (row.voucher_no in self.err_journals)
 			):
 
 				# non-zero oustanding, we must consider this row
@@ -1028,3 +1032,17 @@
 			"data": {"labels": self.ageing_column_labels, "datasets": rows},
 			"type": "percentage",
 		}
+
+	def get_exchange_rate_revaluations(self):
+		je = qb.DocType("Journal Entry")
+		results = (
+			qb.from_(je)
+			.select(je.name)
+			.where(
+				(je.company == self.filters.company)
+				& (je.posting_date.lte(self.filters.report_date))
+				& (je.voucher_type == "Exchange Rate Revaluation")
+			)
+			.run()
+		)
+		self.err_journals = [x[0] for x in results] if results else []
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index bac8bee..97a9c15 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -1,9 +1,10 @@
 import unittest
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days, getdate, today
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, flt, getdate, today
 
+from erpnext import get_default_cost_center
 from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
@@ -17,10 +18,37 @@
 		frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'")
 		frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
 		frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'")
+		frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'")
+		frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'")
+
+		self.create_usd_account()
 
 	def tearDown(self):
 		frappe.db.rollback()
 
+	def create_usd_account(self):
+		name = "Debtors USD"
+		exists = frappe.db.get_list(
+			"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
+		)
+		if exists:
+			self.debtors_usd = exists[0].name
+		else:
+			debtors = frappe.get_doc(
+				"Account",
+				frappe.db.get_list(
+					"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
+				)[0].name,
+			)
+
+			debtors_usd = frappe.new_doc("Account")
+			debtors_usd.company = debtors.company
+			debtors_usd.account_name = "Debtors USD"
+			debtors_usd.account_currency = "USD"
+			debtors_usd.parent_account = debtors.parent_account
+			debtors_usd.account_type = debtors.account_type
+			self.debtors_usd = debtors_usd.save().name
+
 	def test_accounts_receivable(self):
 		filters = {
 			"company": "_Test Company 2",
@@ -33,7 +61,7 @@
 		}
 
 		# check invoice grand total and invoiced column's value for 3 payment terms
-		name = make_sales_invoice()
+		name = make_sales_invoice().name
 		report = execute(filters)
 
 		expected_data = [[100, 30], [100, 50], [100, 20]]
@@ -118,8 +146,74 @@
 			],
 		)
 
+	@change_settings(
+		"Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}
+	)
+	def test_exchange_revaluation_for_party(self):
+		"""
+		Exchange Revaluation for party on Receivable/Payable shoule be included
+		"""
 
-def make_sales_invoice():
+		company = "_Test Company 2"
+		customer = "_Test Customer 2"
+
+		# Using Exchange Gain/Loss account for unrealized as well.
+		company_doc = frappe.get_doc("Company", company)
+		company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account
+		company_doc.save()
+
+		si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+		si.currency = "USD"
+		si.conversion_rate = 0.90
+		si.debit_to = self.debtors_usd
+		si = si.save().submit()
+
+		# Exchange Revaluation
+		err = frappe.new_doc("Exchange Rate Revaluation")
+		err.company = company
+		err.posting_date = today()
+		accounts = err.get_accounts_data()
+		err.extend("accounts", accounts)
+		err.accounts[0].new_exchange_rate = 0.95
+		row = err.accounts[0]
+		row.new_balance_in_base_currency = flt(
+			row.new_exchange_rate * flt(row.balance_in_account_currency)
+		)
+		row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
+		err.set_total_gain_loss()
+		err = err.save().submit()
+
+		# Submit JV for ERR
+		jv = frappe.get_doc(err.make_jv_entry())
+		jv = jv.save()
+		for x in jv.accounts:
+			x.cost_center = get_default_cost_center(jv.company)
+		jv.submit()
+
+		filters = {
+			"company": company,
+			"report_date": today(),
+			"range1": 30,
+			"range2": 60,
+			"range3": 90,
+			"range4": 120,
+		}
+		report = execute(filters)
+
+		expected_data_for_err = [0, -5, 0, 5]
+		row = [x for x in report[1] if x.voucher_type == jv.doctype and x.voucher_no == jv.name][0]
+		self.assertEqual(
+			expected_data_for_err,
+			[
+				row.invoiced,
+				row.paid,
+				row.credit_note,
+				row.outstanding,
+			],
+		)
+
+
+def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
 	frappe.set_user("Administrator")
 
 	si = create_sales_invoice(
@@ -134,22 +228,26 @@
 		do_not_save=1,
 	)
 
-	si.append(
-		"payment_schedule",
-		dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30),
-	)
-	si.append(
-		"payment_schedule",
-		dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50),
-	)
-	si.append(
-		"payment_schedule",
-		dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20),
-	)
+	if not no_payment_schedule:
+		si.append(
+			"payment_schedule",
+			dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30),
+		)
+		si.append(
+			"payment_schedule",
+			dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50),
+		)
+		si.append(
+			"payment_schedule",
+			dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20),
+		)
 
-	si.submit()
+	si = si.save()
 
-	return si.name
+	if not do_not_submit:
+		si = si.submit()
+
+	return si
 
 
 def make_payment(docname):