diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json index 85e516c2..14880fbd 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.json +++ b/crm/fcrm/doctype/crm_deal/crm_deal.json @@ -38,7 +38,7 @@ "column_break_xbyf", "territory", "currency", - "currency_exchange", + "exchange_rate", "annual_revenue", "industry", "person_section", @@ -418,16 +418,17 @@ "label": "Closed On" }, { - "fieldname": "currency_exchange", - "fieldtype": "Link", - "label": "Currency Exchange", - "options": "CRM Currency Exchange" + "default": "1", + "description": "The rate used to convert the deal\u2019s currency to your crm's base currency (set in CRM Settings). It is set once when the currency is first added and doesn't change automatically.", + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-09 14:26:53.986118", + "modified": "2025-07-09 17:58:55.956639", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal", diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index 51bb4280..982e5baa 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -10,6 +10,7 @@ from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla from crm.fcrm.doctype.crm_status_change_log.crm_status_change_log import ( add_status_change_log, ) +from crm.utils import get_historical_exchange_rate class CRMDeal(Document): @@ -28,7 +29,7 @@ class CRMDeal(Document): self.closed_on = frappe.utils.now_datetime() self.validate_forcasting_fields() self.validate_lost_reason() - self.update_currency_exchange() + self.update_exchange_rate() def after_insert(self): if self.deal_owner: @@ -171,27 +172,16 @@ class CRMDeal(Document): elif self.lost_reason == "Other" and not self.lost_notes: frappe.throw(_("Please specify the reason for losing the deal."), frappe.ValidationError) - def update_currency_exchange(self): - if self.has_value_changed("currency") or not self.currency_exchange: - system_currency = frappe.db.get_single_value("System Settings", "currency") - currency_exchange = None + def update_exchange_rate(self): + if self.has_value_changed("currency") or not self.exchange_rate: + system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD" + exchange_rate = 1 if self.currency and self.currency != system_currency: - if not frappe.db.exists( - "CRM Currency Exchange", {"from_currency": self.currency, "to_currency": system_currency} - ): - new_er = frappe.new_doc("CRM Currency Exchange") - new_er.from_currency = self.currency - new_er.to_currency = system_currency - new_er.insert(ignore_permissions=True) - currency_exchange = new_er.name - else: - currency_exchange = frappe.db.get_value( - "CRM Currency Exchange", - {"from_currency": self.currency, "to_currency": system_currency}, - "name", - ) + exchange_rate = get_historical_exchange_rate( + frappe.utils.nowdate(), self.currency, system_currency + ) - currency_exchange and self.db_set("currency_exchange", currency_exchange) + self.db_set("exchange_rate", exchange_rate) @staticmethod def default_list_data(): diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.py b/crm/fcrm/doctype/crm_organization/crm_organization.py index 0da504e2..954994ce 100644 --- a/crm/fcrm/doctype/crm_organization/crm_organization.py +++ b/crm/fcrm/doctype/crm_organization/crm_organization.py @@ -4,32 +4,23 @@ import frappe from frappe.model.document import Document +from crm.utils import get_historical_exchange_rate + class CRMOrganization(Document): def validate(self): - self.update_currency_exchange() + self.update_exchange_rate() - def update_currency_exchange(self): - if self.has_value_changed("currency") or not self.currency_exchange: - system_currency = frappe.db.get_single_value("System Settings", "currency") - currency_exchange = None + def update_exchange_rate(self): + if self.has_value_changed("currency") or not self.exchange_rate: + system_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD" + exchange_rate = 1 if self.currency and self.currency != system_currency: - if not frappe.db.exists( - "CRM Currency Exchange", {"from_currency": self.currency, "to_currency": system_currency} - ): - new_er = frappe.new_doc("CRM Currency Exchange") - new_er.from_currency = self.currency - new_er.to_currency = system_currency - new_er.insert(ignore_permissions=True) - currency_exchange = new_er.name - else: - currency_exchange = frappe.db.get_value( - "CRM Currency Exchange", - {"from_currency": self.currency, "to_currency": system_currency}, - "name", - ) + exchange_rate = get_historical_exchange_rate( + frappe.utils.nowdate(), self.currency, system_currency + ) - currency_exchange and self.db_set("currency_exchange", currency_exchange) + self.db_set("exchange_rate", exchange_rate) @staticmethod def default_list_data(): diff --git a/crm/utils/__init__.py b/crm/utils/__init__.py index 15cbae7c..e1a74991 100644 --- a/crm/utils/__init__.py +++ b/crm/utils/__init__.py @@ -2,6 +2,7 @@ import functools import frappe import phonenumbers +import requests from frappe import _ from frappe.model.docstatus import DocStatus from frappe.model.dynamic_links import get_dynamic_link_map @@ -266,3 +267,29 @@ def sales_user_only(fn): return fn(*args, **kwargs) return wrapper + + +def get_exchange_rate(from_currency, to_currency): + url = f"https://api.frankfurter.app/latest?from={from_currency}&to={to_currency}" + response = requests.get(url) + + if response.status_code == 200: + data = response.json() + rate = data["rates"].get(to_currency) + return rate + else: + frappe.throw(_("Failed to fetch exchange rate from external API. Please try again later.")) + return None + + +def get_historical_exchange_rate(date, from_currency, to_currency): + url = f"https://api.frankfurter.app/{date}?from={from_currency}&to={to_currency}" + response = requests.get(url) + + if response.status_code == 200: + data = response.json() + rate = data["rates"].get(to_currency) + return rate + else: + frappe.throw(_("Failed to fetch historical exchange rate from external API. Please try again later.")) + return None \ No newline at end of file