From b3035ec7d4f087ee7d583ef32b25f2235d648b39 Mon Sep 17 00:00:00 2001 From: Logesh Periyasamy Date: Tue, 5 Aug 2025 20:55:44 +0530 Subject: [PATCH 1/2] Merge pull request #48761 from aerele/exchange-gain-or-loss-on-repost fix: prevent gain or loss entry cancellation upon reposting (cherry picked from commit a8d17b7590265151c24550acfe14b7c2b2961306) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../repost_accounting_ledger.py | 4 +- .../doctype/sales_invoice/sales_invoice.py | 1 - .../sales_invoice/test_sales_invoice.py | 54 +++++++++++++++++++ erpnext/controllers/stock_controller.py | 5 +- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index c76cf5d6e80..94baf0b8588 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -151,7 +151,7 @@ def start_repost(account_repost_doc=str) -> None: if doc.doctype in ["Sales Invoice", "Purchase Invoice"]: if not repost_doc.delete_cancelled_entries: doc.docstatus = 2 - doc.make_gl_entries_on_cancel() + doc.make_gl_entries_on_cancel(from_repost=True) doc.docstatus = 1 if doc.doctype == "Sales Invoice": @@ -163,7 +163,7 @@ def start_repost(account_repost_doc=str) -> None: elif doc.doctype == "Purchase Receipt": if not repost_doc.delete_cancelled_entries: doc.docstatus = 2 - doc.make_gl_entries_on_cancel() + doc.make_gl_entries_on_cancel(from_repost=True) doc.docstatus = 1 doc.make_gl_entries(from_repost=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index dbeeda00f49..08ad0a905ff 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1025,7 +1025,6 @@ class SalesInvoice(SellingController): self.make_exchange_gain_loss_journal() elif self.docstatus == 2: - cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name)) make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) if update_outstanding == "No": diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 2b9af8669d3..192607b0552 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3889,6 +3889,60 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(invoice.outstanding_amount, 0) + def test_system_generated_exchange_gain_or_loss_je_after_repost(self): + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import ( + update_repost_settings, + ) + + update_repost_settings() + + si = create_sales_invoice( + customer="_Test Customer USD", + debit_to="_Test Receivable USD - _TC", + currency="USD", + conversion_rate=80, + ) + + pe = get_payment_entry("Sales Invoice", si.name) + pe.reference_no = "10" + pe.reference_date = nowdate() + pe.paid_from_account_currency = si.currency + pe.paid_to_account_currency = "INR" + pe.source_exchange_rate = 85 + pe.target_exchange_rate = 1 + pe.paid_amount = si.outstanding_amount + pe.received_amount = 85 + pe.insert() + pe.submit() + + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = si.company + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + ral.save() + ral.submit() + + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + q = ( + ( + frappe.qb.from_(je) + .join(jea) + .on(je.name == jea.parent) + .select(je.docstatus) + .where( + (je.voucher_type == "Exchange Gain Or Loss") + & (jea.reference_name == si.name) + & (jea.reference_type == "Sales Invoice") + & (je.is_system_generated == 1) + ) + ) + .limit(1) + .run() + ) + + self.assertEqual(q[0][0], 1) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 38e2d99f8c7..90e76dc7cd1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -520,8 +520,9 @@ class StockController(AccountsController): make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher) - def make_gl_entries_on_cancel(self): - cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name)) + def make_gl_entries_on_cancel(self, from_repost=False): + if not from_repost: + cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name)) if frappe.db.sql( """select name from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", From 5eeaaf47573757386172ed4c51a091a7098ea90c Mon Sep 17 00:00:00 2001 From: Vignesh S Date: Tue, 12 Aug 2025 16:25:59 +0530 Subject: [PATCH 2/2] chore: fix text case failure casued by mergify --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 192607b0552..dd8acec196e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3912,7 +3912,7 @@ class TestSalesInvoice(FrappeTestCase): pe.source_exchange_rate = 85 pe.target_exchange_rate = 1 pe.paid_amount = si.outstanding_amount - pe.received_amount = 85 + pe.received_amount = 8500 pe.insert() pe.submit()