From 31ee3f1923458e939c5cd8c0df05541119e2af0a Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Fri, 26 Jun 2026 13:33:30 +0530 Subject: [PATCH 1/5] fix: set conversion_rate on quotation created from customer --- erpnext/selling/doctype/customer/mapper.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/selling/doctype/customer/mapper.py b/erpnext/selling/doctype/customer/mapper.py index be69e7e5d6c..f6c41ee31a6 100644 --- a/erpnext/selling/doctype/customer/mapper.py +++ b/erpnext/selling/doctype/customer/mapper.py @@ -6,6 +6,8 @@ from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from erpnext.setup.utils import get_exchange_rate + @frappe.whitelist() def make_quotation(source_name: str, target_doc: str | Document | None = None): @@ -32,6 +34,11 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None): target_doc.selling_price_list = price_list if currency: target_doc.currency = currency + company_currency = frappe.get_cached_value("Company", target_doc.company, "default_currency") + if target_doc.company and target_doc.currency != company_currency: + target_doc.conversion_rate = get_exchange_rate( + target_doc.currency, company_currency, target_doc.transaction_date, "for_selling" + ) return target_doc From e61d299e63436390920c0c765273aa0d84639b1d Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Sun, 28 Jun 2026 09:53:38 +0530 Subject: [PATCH 2/5] fix: recalculate totals after setting quotation conversion rate --- erpnext/selling/doctype/customer/mapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/doctype/customer/mapper.py b/erpnext/selling/doctype/customer/mapper.py index f6c41ee31a6..1cff9fbac79 100644 --- a/erpnext/selling/doctype/customer/mapper.py +++ b/erpnext/selling/doctype/customer/mapper.py @@ -39,6 +39,7 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None): target_doc.conversion_rate = get_exchange_rate( target_doc.currency, company_currency, target_doc.transaction_date, "for_selling" ) + target_doc.run_method("calculate_taxes_and_totals") return target_doc From 8446be6518b513db39bf544e74564ddf8ba78fd2 Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Sun, 28 Jun 2026 20:13:53 +0530 Subject: [PATCH 3/5] fix: set currency and price list before computing quotation totals --- erpnext/selling/doctype/customer/mapper.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/customer/mapper.py b/erpnext/selling/doctype/customer/mapper.py index 1cff9fbac79..6d1ed14662d 100644 --- a/erpnext/selling/doctype/customer/mapper.py +++ b/erpnext/selling/doctype/customer/mapper.py @@ -23,9 +23,6 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None): ) target_doc.quotation_to = "Customer" - target_doc.run_method("set_missing_values") - target_doc.run_method("set_other_charges") - target_doc.run_method("calculate_taxes_and_totals") price_list, currency = frappe.db.get_value( "Customer", {"name": source_name}, ["default_price_list", "default_currency"] @@ -39,7 +36,9 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None): target_doc.conversion_rate = get_exchange_rate( target_doc.currency, company_currency, target_doc.transaction_date, "for_selling" ) - target_doc.run_method("calculate_taxes_and_totals") + target_doc.run_method("set_missing_values") + target_doc.run_method("set_other_charges") + target_doc.run_method("calculate_taxes_and_totals") return target_doc From dead28e50ea2496cf2d099c17147a5d25563b1a4 Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Sun, 28 Jun 2026 20:49:56 +0530 Subject: [PATCH 4/5] test: assert quotation from customer uses actual exchange rate --- .../selling/doctype/customer/test_customer.py | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index e4b2ccae4d6..a1b15a1e867 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -5,7 +5,7 @@ import json import frappe -from frappe.utils import flt +from frappe.utils import flt, nowdate from erpnext.accounts.party import get_due_date from erpnext.exceptions import PartyDisabled, PartyFrozen @@ -14,12 +14,53 @@ from erpnext.selling.doctype.customer.customer import ( get_customer_outstanding, ) from erpnext.selling.doctype.customer.mapper import ( + make_quotation, parse_full_name, ) from erpnext.tests.utils import ERPNextTestSuite class TestCustomer(ERPNextTestSuite): + def test_quotation_from_customer_uses_actual_exchange_rate(self): + company = "_Test Company" + company_currency = frappe.get_cached_value("Company", company, "default_currency") + foreign_currency = "USD" if company_currency != "USD" else "EUR" + + frappe.defaults.set_user_default("company", company) + self.addCleanup(frappe.defaults.clear_user_default, "company") + + # Seed a deterministic rate so the test does not depend on the live exchange-rate API. + rate = 83.0 + exchange = frappe.get_doc( + { + "doctype": "Currency Exchange", + "date": nowdate(), + "from_currency": foreign_currency, + "to_currency": company_currency, + "exchange_rate": rate, + "for_selling": 1, + "for_buying": 1, + } + ).insert(ignore_if_duplicate=True) + self.addCleanup(frappe.delete_doc, "Currency Exchange", exchange.name, force=1) + + customer = frappe.get_doc( + { + "doctype": "Customer", + "customer_name": "_Test Customer FX Quotation", + "customer_type": "Company", + "default_currency": foreign_currency, + } + ).insert() + self.addCleanup(frappe.delete_doc, "Customer", customer.name, force=1) + + quotation = make_quotation(customer.name) + + self.assertEqual(quotation.currency, foreign_currency) + self.assertNotEqual(flt(quotation.conversion_rate), 1.0) + self.assertNotEqual(flt(quotation.conversion_rate), 0.0) + self.assertEqual(flt(quotation.conversion_rate), rate) + def test_get_customer_name_dedupes_with_numeric_suffix(self): # When a customer name already exists, get_customer_name appends "- ". The # Postgres branch extracts the suffix with regexp_replace/NULLIF/CAST (pypika's Substring cannot From e4d6c0854ba2388146d7d97092381d38f159d76a Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Wed, 1 Jul 2026 19:28:38 +0530 Subject: [PATCH 5/5] fix: remove redundant conversion_rate --- erpnext/selling/doctype/customer/mapper.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/customer/mapper.py b/erpnext/selling/doctype/customer/mapper.py index 6d1ed14662d..4f230702b6b 100644 --- a/erpnext/selling/doctype/customer/mapper.py +++ b/erpnext/selling/doctype/customer/mapper.py @@ -6,8 +6,6 @@ from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc -from erpnext.setup.utils import get_exchange_rate - @frappe.whitelist() def make_quotation(source_name: str, target_doc: str | Document | None = None): @@ -31,11 +29,7 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None): target_doc.selling_price_list = price_list if currency: target_doc.currency = currency - company_currency = frappe.get_cached_value("Company", target_doc.company, "default_currency") - if target_doc.company and target_doc.currency != company_currency: - target_doc.conversion_rate = get_exchange_rate( - target_doc.currency, company_currency, target_doc.transaction_date, "for_selling" - ) + target_doc.run_method("set_missing_values") target_doc.run_method("set_other_charges") target_doc.run_method("calculate_taxes_and_totals")