From c6bc5f5d7d7f5d0f5c0e07219234e0ad717b8b70 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Dec 2024 13:40:03 +0530 Subject: [PATCH 1/7] refactor: configurable posting date for Exc Gain / Loss journal (cherry picked from commit 3fbd2ca0d9d9cef759669050964e2faa63af2429) --- .../doctype/accounts_settings/accounts_settings.json | 11 ++++++++++- .../doctype/accounts_settings/accounts_settings.py | 1 + .../payment_reconciliation/payment_reconciliation.py | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 6250250a1709..097ae89caaf0 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -47,6 +47,7 @@ "auto_reconciliation_job_trigger", "reconciliation_queue_size", "column_break_resa", + "exchange_gain_loss_posting_date", "invoicing_settings_tab", "accounts_transactions_settings_section", "over_billing_allowance", @@ -523,6 +524,14 @@ "fieldname": "ignore_is_opening_check_for_reporting", "fieldtype": "Check", "label": "Ignore Is Opening check for reporting" + }, + { + "default": "Payment", + "description": "Only applies for Normal Payments", + "fieldname": "exchange_gain_loss_posting_date", + "fieldtype": "Select", + "label": "Posting Date Inheritance for Exchange Gain / Loss", + "options": "Invoice\nPayment" } ], "icon": "icon-cog", @@ -530,7 +539,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-01-18 21:24:19.840745", + "modified": "2025-01-22 17:53:47.968079", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 590422c6224f..f3aca158486c 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -45,6 +45,7 @@ class AccountsSettings(Document): enable_fuzzy_matching: DF.Check enable_immutable_ledger: DF.Check enable_party_matching: DF.Check + exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment"] frozen_accounts_modifier: DF.Link | None general_ledger_remarks_length: DF.Int ignore_account_closing_balance: DF.Check diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index db4a4b0f268a..5577f4fffda2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -424,6 +424,9 @@ def calculate_difference_on_allocation_change(self, payment_entry, invoice, allo def allocate_entries(self, args): self.validate_entries() + exc_gain_loss_posting_date = frappe.db.get_single_value( + "Accounts Settings", "exchange_gain_loss_posting_date", cache=True + ) invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments")) default_exchange_gain_loss_account = frappe.get_cached_value( "Company", self.company, "exchange_gain_loss_account" @@ -450,6 +453,8 @@ def allocate_entries(self, args): 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 exc_gain_loss_posting_date == "Invoice": + res.update({"gain_loss_posting_date": inv.get("invoice_date")}) if pay.get("amount") == 0: entries.append(res) From c127cbac571f4f82f6232b1b593a8cd69e115fdf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Dec 2024 13:40:03 +0530 Subject: [PATCH 2/7] refactor: configurable posting date for Exc Gain / Loss journal (cherry picked from commit 5257413a932f4a9eb1331d5fca6110390531fadf) --- .../accounts/doctype/accounts_settings/accounts_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 097ae89caaf0..a3e2fe82c8e4 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -390,7 +390,7 @@ { "fieldname": "section_break_jpd0", "fieldtype": "Section Break", - "label": "Payment Reconciliations" + "label": "Payment Reconciliation Settings" }, { "default": "0", @@ -568,4 +568,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From d955986342d144de392208a3690f35b3a47204d4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 13:48:09 +0530 Subject: [PATCH 3/7] refactor: allow reconciliation date for exchange gain / loss (cherry picked from commit 95af63e305c51a19bfea43c7f9580c68fb93433e) --- .../doctype/accounts_settings/accounts_settings.json | 6 +++--- .../accounts/doctype/accounts_settings/accounts_settings.py | 2 +- .../payment_reconciliation/payment_reconciliation.py | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index a3e2fe82c8e4..3f343f610e07 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -531,7 +531,7 @@ "fieldname": "exchange_gain_loss_posting_date", "fieldtype": "Select", "label": "Posting Date Inheritance for Exchange Gain / Loss", - "options": "Invoice\nPayment" + "options": "Invoice\nPayment\nReconciliation Date" } ], "icon": "icon-cog", @@ -539,7 +539,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-01-22 17:53:47.968079", + "modified": "2025-01-23 13:15:44.077853", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -568,4 +568,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index f3aca158486c..31249e224550 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -45,7 +45,7 @@ class AccountsSettings(Document): enable_fuzzy_matching: DF.Check enable_immutable_ledger: DF.Check enable_party_matching: DF.Check - exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment"] + exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"] frozen_accounts_modifier: DF.Link | None general_ledger_remarks_length: DF.Int ignore_account_closing_balance: DF.Check diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 5577f4fffda2..d3d89a1c1ccc 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -455,6 +455,8 @@ def allocate_entries(self, args): res.update({"gain_loss_posting_date": pay.get("posting_date")}) if exc_gain_loss_posting_date == "Invoice": res.update({"gain_loss_posting_date": inv.get("invoice_date")}) + elif exc_gain_loss_posting_date == "Reconciliation Date": + res.update({"gain_loss_posting_date": nowdate()}) if pay.get("amount") == 0: entries.append(res) From ccaf0d4b858b5fe65863a0da2c84eab3d19bb882 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 14:10:15 +0530 Subject: [PATCH 4/7] refactor: only apply configuration on normal payments patch to update default value (cherry picked from commit b2c3da135ea85fe245ec9c6066a8e68b42b64f7f) --- .../payment_reconciliation/payment_reconciliation.py | 10 ++++++---- erpnext/controllers/accounts_controller.py | 2 ++ erpnext/patches.txt | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index d3d89a1c1ccc..72aa4905900a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -335,6 +335,7 @@ def add_payment_entries(self, non_reconciled_payments): for payment in non_reconciled_payments: row = self.append("payments", {}) row.update(payment) + row.is_advance = payment.book_advance_payments_in_separate_party_account def get_invoice_entries(self): # Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against @@ -453,10 +454,11 @@ def allocate_entries(self, args): 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 exc_gain_loss_posting_date == "Invoice": - res.update({"gain_loss_posting_date": inv.get("invoice_date")}) - elif exc_gain_loss_posting_date == "Reconciliation Date": - res.update({"gain_loss_posting_date": nowdate()}) + if not pay.get("is_advance"): + if exc_gain_loss_posting_date == "Invoice": + res.update({"gain_loss_posting_date": inv.get("invoice_date")}) + elif exc_gain_loss_posting_date == "Reconciliation Date": + res.update({"gain_loss_posting_date": nowdate()}) if pay.get("amount") == 0: entries.append(res) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7074710dfee0..b687914600bc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2969,6 +2969,7 @@ def get_advance_payment_entries( (payment_ref.allocated_amount).as_("amount"), (payment_ref.name).as_("reference_row"), (payment_ref.reference_name).as_("against_order"), + (payment_entry.book_advance_payments_in_separate_party_account), ) q = q.where(payment_ref.reference_doctype == order_doctype) @@ -3013,6 +3014,7 @@ def get_common_query( (payment_entry.name).as_("reference_name"), payment_entry.posting_date, (payment_entry.remarks).as_("remarks"), + (payment_entry.book_advance_payments_in_separate_party_account), ) .where(payment_entry.payment_type == payment_type) .where(payment_entry.party_type == party_type) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f0cc713ba0be..f40801e27765 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -390,3 +390,4 @@ erpnext.patches.v15_0.update_asset_status_to_work_in_progress erpnext.patches.v15_0.rename_manufacturing_settings_field erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect erpnext.patches.v15_0.sync_auto_reconcile_config +execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") From c90d33acc3cb2b92ab2a4eea0f7f48d43db4c461 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 14:44:40 +0530 Subject: [PATCH 5/7] test: exc gain/loss posting date based on configuration (cherry picked from commit 2f3281579a5b4393ff336e7e2ff274d0b60c5d66) --- .../tests/test_accounts_controller.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index e557a6b2d79c..0c1049f0f0e5 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -9,6 +9,7 @@ from frappe.query_builder.functions import Sum from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, getdate, nowdate +from frappe.utils.data import getdate as convert_to_date from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -868,6 +869,69 @@ def test_16_internal_transfer_at_arms_length_price(self): self.assertEqual(pi.items[0].rate, arms_length_price) self.assertEqual(pi.items[0].valuation_rate, 100) + @IntegrationTestCase.change_settings( + "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} + ) + def test_17_gain_loss_posting_date_for_normal_payment(self): + # Sales Invoice in Foreign Currency + rate = 80 + rate_in_account_currency = 1 + + adv_date = convert_to_date(add_days(nowdate(), -2)) + inv_date = convert_to_date(add_days(nowdate(), -1)) + + si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=rate_in_account_currency) + + # Test payments with different exchange rates + pe = self.create_payment_entry(posting_date=adv_date, amount=1, source_exc_rate=75.1).save().submit() + + pr = self.create_payment_reconciliation() + pr.from_invoice_date = add_days(nowdate(), -1) + pr.to_invoice_date = nowdate() + pr.from_payment_date = add_days(nowdate(), -2) + pr.to_payment_date = nowdate() + + 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.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # Outstanding in both currencies should be '0' + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # 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( + getdate(nowdate()), frappe.db.get_value("Journal Entry", exc_je_for_pe[0].parent, "posting_date") + ) + # Cancel Payment + pe.reload() + pe.cancel() + + # outstanding should be same as grand total + si.reload() + self.assertEqual(si.outstanding_amount, rate_in_account_currency) + self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency) + + # 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) From 1c10e4e92fbf6e2e8a34ed1a65ff57637bb8b7f9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 16:09:54 +0530 Subject: [PATCH 6/7] refactor: support JE posting date in semi-auto reconciilation tool (cherry picked from commit a71718883e933c7eadc15842cae2dd59c6b1d005) # Conflicts: # erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json --- ...process_payment_reconciliation_log_allocations.json | 10 ++++++++++ .../process_payment_reconciliation_log_allocations.py | 1 + 2 files changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json index b97d73886a95..5c771c54f52b 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -20,6 +20,7 @@ "is_advance", "section_break_5", "difference_amount", + "gain_loss_posting_date", "column_break_7", "difference_account", "exchange_rate", @@ -153,11 +154,20 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Reconciled" + }, + { + "fieldname": "gain_loss_posting_date", + "fieldtype": "Date", + "label": "Difference Posting Date" } ], "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-03-20 21:05:43.121945", +======= + "modified": "2025-01-23 16:09:01.058574", +>>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log Allocations", diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py index da02e1a41e66..ca1785afdae7 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py @@ -20,6 +20,7 @@ class ProcessPaymentReconciliationLogAllocations(Document): difference_account: DF.Link | None difference_amount: DF.Currency exchange_rate: DF.Float + gain_loss_posting_date: DF.Date | None invoice_number: DF.DynamicLink invoice_type: DF.Link is_advance: DF.Data | None From 2f60f235a8700390fb2abd7b1a7e84ce1c1c736c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Jan 2025 10:24:57 +0530 Subject: [PATCH 7/7] fix: decorator and merge conflicts --- .../process_payment_reconciliation_log_allocations.json | 6 +----- erpnext/controllers/tests/test_accounts_controller.py | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json index 5c771c54f52b..9d20ca3a87dd 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -163,11 +163,7 @@ ], "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-03-20 21:05:43.121945", -======= "modified": "2025-01-23 16:09:01.058574", ->>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log Allocations", @@ -177,4 +173,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 0c1049f0f0e5..c45923b2fedb 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -869,9 +869,7 @@ def test_16_internal_transfer_at_arms_length_price(self): self.assertEqual(pi.items[0].rate, arms_length_price) self.assertEqual(pi.items[0].valuation_rate, 100) - @IntegrationTestCase.change_settings( - "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} - ) + @change_settings("Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"}) def test_17_gain_loss_posting_date_for_normal_payment(self): # Sales Invoice in Foreign Currency rate = 80