From 7be3aa3158a752d30def9a9dabcecccc2ee744f8 Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Mon, 5 Sep 2016 23:35:00 +0100 Subject: [PATCH] Changes to allow exchange rates on a specific day to be stored in Currency Exchange and to be pulled by the necessary transactions based on the posting date. --- erpnext/public/js/controllers/transaction.js | 5 +- .../currency_exchange/currency_exchange.json | 48 +++++- .../currency_exchange/currency_exchange.py | 14 +- .../currency_exchange/test_records.json | 3 + erpnext/setup/utils.py | 150 +++++++++--------- 5 files changed, 131 insertions(+), 89 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 34cbbae31b..6a0d0f8b7f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -424,7 +424,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc if(this.frm.doc.currency && this.frm.doc.currency !== company_currency && !this.frm.doc.ignore_pricing_rule) { - this.get_exchange_rate(this.frm.doc.currency, company_currency, + this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.currency, company_currency, function(exchange_rate) { me.frm.set_value("conversion_rate", exchange_rate); }); @@ -452,10 +452,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - get_exchange_rate: function(from_currency, to_currency, callback) { + get_exchange_rate: function(posting_date, from_currency, to_currency, callback) { return frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { + posting_date: posting_date, from_currency: from_currency, to_currency: to_currency }, diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.json b/erpnext/setup/doctype/currency_exchange/currency_exchange.json index 1209bc1e70..4c62e9bc96 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.json +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.json @@ -15,13 +15,41 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "fieldname": "date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "5" + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "from_currency", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 1, + "in_list_view": 0, "label": "From Currency", "length": 0, "no_copy": 0, @@ -34,19 +62,21 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "unique": 0, + "width": "3" }, { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "to_currency", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 1, + "in_list_view": 0, "label": "To Currency", "length": 0, "no_copy": 0, @@ -59,12 +89,14 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "unique": 0, + "width": "3" }, { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "exchange_rate", "fieldtype": "Float", "hidden": 0, @@ -84,7 +116,8 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "unique": 0, + "width": "3" } ], "hide_heading": 0, @@ -98,7 +131,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-07-25 05:24:26.264021", + "modified": "2016-09-05 22:47:38.746711", "modified_by": "Administrator", "module": "Setup", "name": "Currency Exchange", @@ -188,5 +221,8 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, + "sort_field": "name", + "sort_order": "DESC", + "title_field": "", "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py index 6022812975..7f1a43c0ee 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py @@ -7,13 +7,15 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from frappe.utils import get_datetime, get_datetime_str, formatdate class CurrencyExchange(Document): - def autoname(self): - self.name = self.from_currency + "-" + self.to_currency + def autoname(self): + self.name = formatdate(get_datetime_str(self.date),"yyyy-MM-dd") + "-" + self.from_currency + "-" + self.to_currency + #self.name = self.date + "-" + self.from_currency + "-" + self.to_currency - def validate(self): - self.validate_value("exchange_rate", ">", 0) + def validate(self): + self.validate_value("exchange_rate", ">", 0) - if self.from_currency == self.to_currency: - frappe.throw(_("From Currency and To Currency cannot be same")) + if self.from_currency == self.to_currency: + frappe.throw(_("From Currency and To Currency cannot be same")) diff --git a/erpnext/setup/doctype/currency_exchange/test_records.json b/erpnext/setup/doctype/currency_exchange/test_records.json index 784bf262c0..4bfcd2e30c 100644 --- a/erpnext/setup/doctype/currency_exchange/test_records.json +++ b/erpnext/setup/doctype/currency_exchange/test_records.json @@ -1,18 +1,21 @@ [ { "doctype": "Currency Exchange", + "date": "01-01-2016" "exchange_rate": 60.0, "from_currency": "USD", "to_currency": "INR" }, { "doctype": "Currency Exchange", + "date": "01-01-2016" "exchange_rate": 0.773, "from_currency": "USD", "to_currency": "EUR" }, { "doctype": "Currency Exchange", + "date": "01-01-2016" "exchange_rate": 0.0167, "from_currency": "INR", "to_currency": "USD" diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index eda2042fd5..c340cac20a 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -5,95 +5,95 @@ from __future__ import unicode_literals import frappe from frappe import _, throw from frappe.utils import flt +from frappe.utils import get_datetime, get_datetime_str def get_company_currency(company): - currency = frappe.db.get_value("Company", company, "default_currency", cache=True) - if not currency: - currency = frappe.db.get_default("currency") - if not currency: - throw(_('Please specify Default Currency in Company Master and Global Defaults')) + currency = frappe.db.get_value("Company", company, "default_currency", cache=True) + if not currency: + currency = frappe.db.get_default("currency") + if not currency: + throw(_('Please specify Default Currency in Company Master and Global Defaults')) - return currency + return currency def get_root_of(doctype): - """Get root element of a DocType with a tree structure""" - result = frappe.db.sql_list("""select name from `tab%s` - where lft=1 and rgt=(select max(rgt) from `tab%s` where docstatus < 2)""" % - (doctype, doctype)) - return result[0] if result else None + """Get root element of a DocType with a tree structure""" + result = frappe.db.sql_list("""select name from `tab%s` + where lft=1 and rgt=(select max(rgt) from `tab%s` where docstatus < 2)""" % + (doctype, doctype)) + return result[0] if result else None def get_ancestors_of(doctype, name): - """Get ancestor elements of a DocType with a tree structure""" - lft, rgt = frappe.db.get_value(doctype, name, ["lft", "rgt"]) - result = frappe.db.sql_list("""select name from `tab%s` - where lft<%s and rgt>%s order by lft desc""" % (doctype, "%s", "%s"), (lft, rgt)) - return result or [] + """Get ancestor elements of a DocType with a tree structure""" + lft, rgt = frappe.db.get_value(doctype, name, ["lft", "rgt"]) + result = frappe.db.sql_list("""select name from `tab%s` + where lft<%s and rgt>%s order by lft desc""" % (doctype, "%s", "%s"), (lft, rgt)) + return result or [] def before_tests(): - frappe.clear_cache() - # complete setup if missing - from frappe.desk.page.setup_wizard.setup_wizard import setup_complete - if not frappe.get_list("Company"): - setup_complete({ - "currency" :"USD", - "first_name" :"Test", - "last_name" :"User", - "company_name" :"Wind Power LLC", - "timezone" :"America/New_York", - "company_abbr" :"WP", - "industry" :"Manufacturing", - "country" :"United States", - "fy_start_date" :"2011-01-01", - "fy_end_date" :"2011-12-31", - "language" :"english", - "company_tagline" :"Testing", - "email" :"test@erpnext.com", - "password" :"test", - "chart_of_accounts" : "Standard", - "domain" : "Manufacturing", - - }) + frappe.clear_cache() + # complete setup if missing + from frappe.desk.page.setup_wizard.setup_wizard import setup_complete + if not frappe.get_list("Company"): + setup_complete({ + "currency" :"USD", + "first_name" :"Test", + "last_name" :"User", + "company_name" :"Wind Power LLC", + "timezone" :"America/New_York", + "company_abbr" :"WP", + "industry" :"Manufacturing", + "country" :"United States", + "fy_start_date" :"2011-01-01", + "fy_end_date" :"2011-12-31", + "language" :"english", + "company_tagline" :"Testing", + "email" :"test@erpnext.com", + "password" :"test", + "chart_of_accounts" : "Standard", + "domain" : "Manufacturing", + + }) - frappe.db.sql("delete from `tabLeave Allocation`") - frappe.db.sql("delete from `tabLeave Application`") - frappe.db.sql("delete from `tabSalary Slip`") - frappe.db.sql("delete from `tabItem Price`") + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Application`") + frappe.db.sql("delete from `tabSalary Slip`") + frappe.db.sql("delete from `tabItem Price`") - frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0) + frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0) - frappe.db.commit() + frappe.db.commit() @frappe.whitelist() -def get_exchange_rate(from_currency, to_currency): - if not (from_currency and to_currency): - return - - if from_currency == to_currency: - return 1 - - exchange = "%s-%s" % (from_currency, to_currency) - value = flt(frappe.db.get_value("Currency Exchange", exchange, "exchange_rate")) +def get_exchange_rate(posting_date, from_currency, to_currency): + if not (posting_date and from_currency and to_currency): + return + + if from_currency == to_currency: + return 1 + + #Get all entries in Currency Exchange with from_currency and to_currency + entries = frappe.get_all("Currency Exchange", fields = ["*"], filters=[["date", "<=", get_datetime_str(posting_date)], ["from_currency", "=", from_currency], ["to_currency", "=", to_currency]], order_by="date desc") + if entries: + return flt(entries[0].exchange_rate) - if not value: - try: - cache = frappe.cache() - key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency) - value = cache.get(key) + try: + cache = frappe.cache() + key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency) + value = cache.get(key) - if not value: - import requests - response = requests.get("http://api.fixer.io/latest", params={ - "base": from_currency, - "symbols": to_currency - }) - # expire in 6 hours - response.raise_for_status() - value = response.json()["rates"][to_currency] - cache.setex(key, value, 6 * 60 * 60) + if not value: + import requests + response = requests.get("http://api.fixer.io/latest", params={ + "base": from_currency, + "symbols": to_currency + }) + # expire in 6 hours + response.raise_for_status() + value = response.json()["rates"][to_currency] + cache.setex(key, value, 6 * 60 * 60) - return flt(value) - except: - frappe.msgprint(_("Unable to find exchange rate for {0} to {1}").format(from_currency, to_currency)) - return 0.0 - else: - return value + return flt(value) + except: + frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, posting_date)) + return 0.0 \ No newline at end of file