From 6f9a8ff101dd0ce66b61d0c7b7742a171515caec Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sun, 14 Jun 2026 20:32:56 +0530 Subject: [PATCH] fix: don't convert item margin on exchange rate refresh Changing the transaction/posting date re-triggers the `currency` handler, which fetches a fresh exchange rate and, whenever it differs from the current one, divided every Amount-type item margin and Actual tax charge by the new rate. `margin_rate_or_amount` is an amount in the transaction currency, so dividing it is only meaningful when the document actually switches currency; on a mere rate refresh it silently shrinks the margin every time. Track the currency the rendered document is denominated in and convert margins/actual charges only on a real currency change, while still updating `conversion_rate` so base amounts recalculate correctly. Also remove the duplicated `currency()` override in quotation.js: it re-ran the same fetch-and-convert block after `super.currency()` (double converting margins) and lacked the `load_after_mapping` guard. The base handler already covers Quotation via `transaction_date`. Fixes #45210 --- erpnext/public/js/controllers/transaction.js | 18 +++++++++++++++-- .../selling/doctype/quotation/quotation.js | 20 ------------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index deaf50846de..5b52c1dd66e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -600,6 +600,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe refresh() { erpnext.toggle_naming_series(); erpnext.hide_company(this.frm); + // Remember the currency the rendered document is denominated in, so that a + // real currency change can be told apart from a mere exchange rate refresh + // (e.g. triggered by a date change). + this._doc_currency = this.frm.doc.currency; this.set_dynamic_labels(); this.setup_sms(); this.setup_quality_inspection(); @@ -1470,6 +1474,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let me = this; this.set_dynamic_labels(); let company_currency = this.get_company_currency(); + // Currency the stored margins/actual charges are denominated in, captured + // before this trigger updates the tracker for the next one. + let previous_currency = this._doc_currency; + this._doc_currency = this.frm.doc.currency; // Added `load_after_mapping` to determine if document is loading after mapping from another doc if ( this.frm.doc.currency && @@ -1482,8 +1490,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe company_currency, function (exchange_rate) { if (exchange_rate != me.frm.doc.conversion_rate) { - me.set_margin_amount_based_on_currency(exchange_rate); - me.set_actual_charges_based_on_currency(exchange_rate); + // Margins and actual charges are amounts in the transaction + // currency; convert them only when the currency itself changed, + // not when just the exchange rate was refreshed (e.g. by a date + // change), otherwise the entered margin keeps shrinking. + if (previous_currency !== me.frm.doc.currency) { + me.set_margin_amount_based_on_currency(exchange_rate); + me.set_actual_charges_based_on_currency(exchange_rate); + } me.frm.set_value("conversion_rate", exchange_rate); } } diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 1e6c729b892..1968968f3c8 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -382,26 +382,6 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. dialog.show(); } - currency() { - super.currency(); - let me = this; - const company_currency = this.get_company_currency(); - if (this.frm.doc.currency && this.frm.doc.currency !== company_currency) { - this.get_exchange_rate( - this.frm.doc.transaction_date, - this.frm.doc.currency, - company_currency, - function (exchange_rate) { - if (exchange_rate != me.frm.doc.conversion_rate) { - me.set_margin_amount_based_on_currency(exchange_rate); - me.set_actual_charges_based_on_currency(exchange_rate); - me.frm.set_value("conversion_rate", exchange_rate); - } - } - ); - } - } - disable_customer_if_creating_from_opportunity(doc) { if (doc.opportunity) { this.frm.set_df_property("party_name", "read_only", 1);