Merge pull request #29115 from deepeshgarg007/deferred_revenue_multi_currency_jv

fix: Deferred revenue booking for multi currency invoices via JV
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 22c81dd..9e2cdff 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -254,11 +254,13 @@
 	enable_check = "enable_deferred_revenue" \
 		if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
 
+	accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto')
+
 	def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
 		start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
 		if not (start_date and end_date): return
 
-		account_currency = get_account_currency(item.expense_account)
+		account_currency = get_account_currency(item.expense_account or item.income_account)
 		if doc.doctype == "Sales Invoice":
 			against, project = doc.customer, doc.project
 			credit_account, debit_account = item.income_account, item.deferred_revenue_account
@@ -279,6 +281,10 @@
 		if not amount:
 			return
 
+		# check if books nor frozen till endate:
+		if getdate(end_date) >= getdate(accounts_frozen_upto):
+			end_date = get_last_day(add_days(accounts_frozen_upto, 1))
+
 		if via_journal_entry:
 			book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
 				base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
@@ -406,8 +412,6 @@
 		'account': credit_account,
 		'credit': base_amount,
 		'credit_in_account_currency': amount,
-		'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
-		'party': against,
 		'account_currency': account_currency,
 		'reference_name': doc.name,
 		'reference_type': doc.doctype,
@@ -420,8 +424,6 @@
 		'account': debit_account,
 		'debit': base_amount,
 		'debit_in_account_currency': amount,
-		'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
-		'party': against,
 		'account_currency': account_currency,
 		'reference_name': doc.name,
 		'reference_type': doc.doctype,
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 8fc4e8c..ac8ab31 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -407,13 +407,14 @@
 						debit_or_credit = 'Debit' if d.debit else 'Credit'
 						party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
 							debit_or_credit)
+						against_voucher = ['', against_voucher[1]]
 					else:
 						if d.reference_type == "Sales Invoice":
 							party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
 						else:
 							party_account = against_voucher[1]
 
-					if (against_voucher[0] != d.party or party_account != d.account):
+					if (against_voucher[0] != cstr(d.party) or party_account != d.account):
 						frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
 							.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
 								d.reference_type, d.reference_name))
@@ -478,13 +479,22 @@
 
 	def set_against_account(self):
 		accounts_debited, accounts_credited = [], []
-		for d in self.get("accounts"):
-			if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
-			if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
+		if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
+			for d in self.get('accounts'):
+				if d.reference_type == 'Sales Invoice':
+					field = 'customer'
+				else:
+					field = 'supplier'
 
-		for d in self.get("accounts"):
-			if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
-			if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
+				d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
+		else:
+			for d in self.get("accounts"):
+				if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
+				if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
+
+			for d in self.get("accounts"):
+				if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
+				if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
 
 	def validate_debit_credit_amount(self):
 		for d in self.get('accounts'):
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c02c80a..cfa42f6 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1781,47 +1781,6 @@
 
 		check_gl_entries(self, si.name, expected_gle, "2019-01-30")
 
-	def test_deferred_revenue_post_account_freeze_upto_by_admin(self):
-		frappe.set_user("Administrator")
-
-		frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
-		frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
-
-		deferred_account = create_account(account_name="Deferred Revenue",
-			parent_account="Current Liabilities - _TC", company="_Test Company")
-
-		item = create_item("_Test Item for Deferred Accounting")
-		item.enable_deferred_revenue = 1
-		item.deferred_revenue_account = deferred_account
-		item.no_of_months = 12
-		item.save()
-
-		si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True)
-		si.items[0].enable_deferred_revenue = 1
-		si.items[0].service_start_date = "2019-01-10"
-		si.items[0].service_end_date = "2019-03-15"
-		si.items[0].deferred_revenue_account = deferred_account
-		si.save()
-		si.submit()
-
-		frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
-		frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager')
-
-		pda1 = frappe.get_doc(dict(
-			doctype='Process Deferred Accounting',
-			posting_date=nowdate(),
-			start_date="2019-01-01",
-			end_date="2019-03-31",
-			type="Income",
-			company="_Test Company"
-		))
-
-		pda1.insert()
-		self.assertRaises(frappe.ValidationError, pda1.submit)
-
-		frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
-		frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
-
 	def test_fixed_deferred_revenue(self):
 		deferred_account = create_account(account_name="Deferred Revenue",
 			parent_account="Current Liabilities - _TC", company="_Test Company")
@@ -2482,6 +2441,74 @@
 
 		frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
 
+	def test_multi_currency_deferred_revenue_via_journal_entry(self):
+		deferred_account = create_account(account_name="Deferred Revenue",
+			parent_account="Current Liabilities - _TC", company="_Test Company")
+
+		acc_settings = frappe.get_single('Accounts Settings')
+		acc_settings.book_deferred_entries_via_journal_entry = 1
+		acc_settings.submit_journal_entries = 1
+		acc_settings.save()
+
+		item = create_item("_Test Item for Deferred Accounting")
+		item.enable_deferred_expense = 1
+		item.deferred_revenue_account = deferred_account
+		item.save()
+
+		si = create_sales_invoice(customer='_Test Customer USD', currency='USD',
+			item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True)
+
+		si.set_posting_time = 1
+		si.posting_date = '2019-01-01'
+		si.debit_to = '_Test Receivable USD - _TC'
+		si.items[0].enable_deferred_revenue = 1
+		si.items[0].service_start_date = "2019-01-01"
+		si.items[0].service_end_date = "2019-03-30"
+		si.items[0].deferred_expense_account = deferred_account
+		si.save()
+		si.submit()
+
+		frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
+
+		pda1 = frappe.get_doc(dict(
+			doctype='Process Deferred Accounting',
+			posting_date=nowdate(),
+			start_date="2019-01-01",
+			end_date="2019-03-31",
+			type="Income",
+			company="_Test Company"
+		))
+
+		pda1.insert()
+		pda1.submit()
+
+		expected_gle = [
+			["Sales - _TC", 0.0, 2089.89, "2019-01-28"],
+			[deferred_account, 2089.89, 0.0, "2019-01-28"],
+			["Sales - _TC", 0.0, 1887.64, "2019-02-28"],
+			[deferred_account, 1887.64, 0.0, "2019-02-28"],
+			["Sales - _TC", 0.0, 2022.47, "2019-03-15"],
+			[deferred_account, 2022.47, 0.0, "2019-03-15"]
+		]
+
+		gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+			from `tabGL Entry`
+			where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
+			order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1)
+
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_gle[i][0], gle.account)
+			self.assertEqual(expected_gle[i][1], gle.credit)
+			self.assertEqual(expected_gle[i][2], gle.debit)
+			self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
+
+		acc_settings = frappe.get_single('Accounts Settings')
+		acc_settings.book_deferred_entries_via_journal_entry = 0
+		acc_settings.submit_journal_entriessubmit_journal_entries = 0
+		acc_settings.save()
+
+		frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
+
 def get_sales_invoice_for_e_invoice():
 	si = make_sales_invoice_for_ewaybill()
 	si.naming_series = 'INV-2020-.#####'
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index eab9e12..4775f56 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -185,8 +185,6 @@
 					frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
 				elif getdate(self.posting_date) > getdate(d.service_end_date):
 					frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
-				elif getdate(self.posting_date) > getdate(d.service_start_date):
-					frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx))
 
 	def validate_invoice_documents_schedule(self):
 		self.validate_payment_schedule_dates()