diff --git a/crm/fcrm/doctype/crm_currency_exchange/__init__.py b/crm/fcrm/doctype/crm_currency_exchange/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.js b/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.js new file mode 100644 index 00000000..fe22715d --- /dev/null +++ b/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.js @@ -0,0 +1,14 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("CRM Currency Exchange", { + refresh(frm) { + frm.add_custom_button(__("Update exchange rate"), () => { + frm.trigger("update_exchange_rate"); + }); + }, + async update_exchange_rate(frm) { + let r = await frm.call("update_exchange_rate"); + r.message && frappe.msgprint(__("Exchange rate updated successfully")); + } +}); diff --git a/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.json b/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.json new file mode 100644 index 00000000..205c25c5 --- /dev/null +++ b/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.json @@ -0,0 +1,106 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:{from_currency}-{to_currency}", + "creation": "2025-07-09 11:21:32.170363", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "from_currency", + "column_break_knzx", + "to_currency", + "section_break_nsen", + "exchange_rate" + ], + "fields": [ + { + "fieldname": "from_currency", + "fieldtype": "Link", + "in_list_view": 1, + "label": "From Currency", + "options": "Currency" + }, + { + "fieldname": "to_currency", + "fieldtype": "Link", + "in_list_view": 1, + "label": "To Currency", + "options": "Currency" + }, + { + "description": "1 {from_currency} = {exchange_rate} {to_currency}. E.g. 1 USD = 84 INR", + "fieldname": "exchange_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Exchange Rate" + }, + { + "fieldname": "column_break_knzx", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_nsen", + "fieldtype": "Section Break" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-07-09 14:36:54.390864", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM Currency Exchange", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 + } + ], + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.py b/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.py new file mode 100644 index 00000000..59ca1cea --- /dev/null +++ b/crm/fcrm/doctype/crm_currency_exchange/crm_currency_exchange.py @@ -0,0 +1,41 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt +import frappe +import requests +from frappe import _ +from frappe.model.document import Document + + +class CRMCurrencyExchange(Document): + def validate(self): + if self.exchange_rate and self.exchange_rate <= 0: + frappe.throw(_("Exchange Rate must be a positive number.")) + + if self.from_currency == self.to_currency: + frappe.throw(_("From Currency and To Currency cannot be the same.")) + + def on_update(self): + if not self.exchange_rate: + self.update_exchange_rate() + + if self.exchange_rate <= 0: + frappe.throw(_("Exchange Rate must be a positive number.")) + + @frappe.whitelist() + def update_exchange_rate(self): + exchange_rate = get_exchange_rate(self.from_currency, self.to_currency) + self.db_set("exchange_rate", exchange_rate) + return exchange_rate + + +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 diff --git a/crm/fcrm/doctype/crm_currency_exchange/test_crm_currency_exchange.py b/crm/fcrm/doctype/crm_currency_exchange/test_crm_currency_exchange.py new file mode 100644 index 00000000..7b9cb8c9 --- /dev/null +++ b/crm/fcrm/doctype/crm_currency_exchange/test_crm_currency_exchange.py @@ -0,0 +1,29 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class UnitTestCRMCurrencyExchange(UnitTestCase): + """ + Unit tests for CRMCurrencyExchange. + Use this class for testing individual functions and methods. + """ + + pass + + +class IntegrationTestCRMCurrencyExchange(IntegrationTestCase): + """ + Integration tests for CRMCurrencyExchange. + Use this class for testing interactions between multiple components. + """ + + pass