diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index e0d142288e8..cc99fe7b583 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -254,6 +254,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.data = []; const dialog = new frappe.ui.Dialog({ title: __("Select Difference Account"), + size: "extra-large", fields: [ { fieldname: "allocation", @@ -279,6 +280,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo in_list_view: 1, read_only: 1, }, + { + fieldtype: "Date", + fieldname: "gain_loss_posting_date", + label: __("Posting Date"), + in_list_view: 1, + reqd: 1, + }, { fieldtype: "Link", options: "Account", @@ -319,6 +327,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo "difference_account", d.difference_account ); + frappe.model.set_value( + "Payment Reconciliation Allocation", + d.docname, + "gain_loss_posting_date", + d.gain_loss_posting_date + ); }); this.reconcile_payment_entries(); @@ -334,6 +348,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo reference_name: d.reference_name, difference_amount: d.difference_amount, difference_account: d.difference_account, + gain_loss_posting_date: d.gain_loss_posting_date, }); } }); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 2c4952a0c66..dcb1a16dba4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -446,6 +446,7 @@ class PaymentReconciliation(Document): res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"]) res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") + res.update({"gain_loss_posting_date": pay.get("posting_date")}) if pay.get("amount") == 0: entries.append(res) @@ -562,6 +563,7 @@ class PaymentReconciliation(Document): "allocated_amount": flt(row.get("allocated_amount")), "difference_amount": flt(row.get("difference_amount")), "difference_account": row.get("difference_account"), + "difference_posting_date": row.get("gain_loss_posting_date"), "cost_center": row.get("cost_center"), } ) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index a553b982d14..3f85b213500 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -19,6 +19,7 @@ "is_advance", "section_break_5", "difference_amount", + "gain_loss_posting_date", "column_break_7", "difference_account", "exchange_rate", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fe4500a41e5..185ef4c22f0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1360,7 +1360,9 @@ class AccountsController(TransactionBase): self.name, arg.get("referenced_row"), ): - posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date") + posting_date = arg.get("difference_posting_date") or frappe.db.get_value( + arg.voucher_type, arg.voucher_no, "posting_date" + ) je = create_gain_loss_journal( self.company, posting_date, @@ -1444,7 +1446,7 @@ class AccountsController(TransactionBase): je = create_gain_loss_journal( self.company, - self.posting_date, + args.get("difference_posting_date") if args else self.posting_date, self.party_type, self.party, party_account, diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 65b2696e79a..2170628361d 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -7,7 +7,7 @@ import frappe from frappe import qb from frappe.query_builder.functions import Sum from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowdate +from frappe.utils import add_days, flt, getdate, nowdate from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -616,6 +616,73 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) + def test_15_gain_loss_on_different_posting_date(self): + # Invoice in Foreign Currency + si = self.create_sales_invoice( + posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1 + ) + # Payment + pe = ( + self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75) + .save() + .submit() + ) + + # There should be outstanding in both currencies + si.reload() + self.assertEqual(si.outstanding_amount, 2) + self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0) + + # Reconcile the remaining amount + pr = frappe.get_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Customer" + pr.party = self.customer + pr.receivable_payable_account = self.debit_usd + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1) + pr.reconcile() + + # Exchange Gain/Loss Journal should've been created. + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertNotEqual(exc_je_for_si, []) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_pe), 1) + self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0]) + + self.assertEqual( + frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"), + getdate(add_days(nowdate(), 1)), + ) + + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # There should be no outstanding + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Cancel Payment + pe.reload() + pe.cancel() + + si.reload() + self.assertEqual(si.outstanding_amount, 2) + self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertEqual(exc_je_for_si, []) + self.assertEqual(exc_je_for_pe, []) + def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)