From 7be3aa3158a752d30def9a9dabcecccc2ee744f8 Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Mon, 5 Sep 2016 23:35:00 +0100 Subject: [PATCH 01/14] 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 From 10024e13ecfbb24d53cd6dff49efcbdb80dcb465 Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Tue, 6 Sep 2016 00:26:27 +0100 Subject: [PATCH 02/14] Correction to test_records.json for Currency Exchange doctype --- erpnext/setup/doctype/currency_exchange/test_records.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/doctype/currency_exchange/test_records.json b/erpnext/setup/doctype/currency_exchange/test_records.json index 4bfcd2e30c..17ca1a5f24 100644 --- a/erpnext/setup/doctype/currency_exchange/test_records.json +++ b/erpnext/setup/doctype/currency_exchange/test_records.json @@ -1,21 +1,21 @@ [ { "doctype": "Currency Exchange", - "date": "01-01-2016" + "date": "01-01-2016", "exchange_rate": 60.0, "from_currency": "USD", "to_currency": "INR" }, { "doctype": "Currency Exchange", - "date": "01-01-2016" + "date": "01-01-2016", "exchange_rate": 0.773, "from_currency": "USD", "to_currency": "EUR" }, { "doctype": "Currency Exchange", - "date": "01-01-2016" + "date": "01-01-2016", "exchange_rate": 0.0167, "from_currency": "INR", "to_currency": "USD" From fbf27b5031768d27d8b7376ad9e6a4dcf5d3af4f Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Wed, 7 Sep 2016 03:06:08 +0100 Subject: [PATCH 03/14] Further corrections to Currency Exchange table --- .../doctype/journal_entry/journal_entry.js | 1 + .../doctype/journal_entry/journal_entry.py | 24 ++++++++++++------- .../doctype/payment_entry/payment_entry.js | 2 ++ .../doctype/payment_entry/payment_entry.py | 17 +++++++------ .../payment_request/test_payment_request.py | 3 ++- erpnext/accounts/doctype/sales_invoice/pos.py | 2 +- .../quoted_item_comparison.py | 3 ++- .../crm/doctype/opportunity/opportunity.py | 2 +- erpnext/demo/user/purchase.py | 3 ++- erpnext/demo/user/sales.py | 3 ++- erpnext/public/js/controllers/transaction.js | 2 +- 11 files changed, 40 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 13f2f61971..57f6e25f01 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -421,6 +421,7 @@ $.extend(erpnext.journal_entry, { frappe.call({ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", args: { + posting_date: frm.doc.posting_date, account: row.account, account_currency: row.account_currency, company: frm.doc.company, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4e00c32808..2a6d276b03 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -310,8 +310,9 @@ class JournalEntry(AccountsController): if d.account_currency == self.company_currency: d.exchange_rate = 1 elif not d.exchange_rate or d.exchange_rate == 1 or \ - (d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name): - d.exchange_rate = get_exchange_rate(d.account, d.account_currency, self.company, + (d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name and d.posting_date): + # Modified to include the posting date for which to retreive the exchange rate + d.exchange_rate = get_exchange_rate(self.posting_date, d.account, d.account_currency, self.company, d.reference_type, d.reference_name, d.debit, d.credit, d.exchange_rate) if not d.exchange_rate: @@ -631,7 +632,8 @@ def get_payment_entry(ref_doc, args): cost_center = frappe.db.get_value("Company", ref_doc.company, "cost_center") exchange_rate = 1 if args.get("party_account"): - exchange_rate = get_exchange_rate(args.get("party_account"), args.get("party_account_currency"), + # Modified to include the posting date for which the exchange rate is required. Assumed to be the posting date in the reference document + exchange_rate = get_exchange_rate(ref_doc.posting_date, args.get("party_account"), args.get("party_account_currency"), ref_doc.company, ref_doc.doctype, ref_doc.name) je = frappe.new_doc("Journal Entry") @@ -664,7 +666,9 @@ def get_payment_entry(ref_doc, args): bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account")) if bank_account: bank_row.update(bank_account) - bank_row.exchange_rate = get_exchange_rate(bank_account["account"], + # Modified to include the posting date for which the exchange rate is required. Assumed to be the posting date of the + # reference date + bank_row.exchange_rate = get_exchange_rate(ref_doc.posting_date, bank_account["account"], bank_account["account_currency"], ref_doc.company) bank_row.cost_center = cost_center @@ -787,7 +791,9 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi "party_type": party_type, "account_type": account_details.account_type, "account_currency": account_details.account_currency or company_currency, - "exchange_rate": get_exchange_rate(account, account_details.account_currency, + # The date used to retreive the exchange rate here is the date passed in as an argument to this function. + # It is assumed to be the date on which the balance is sought + "exchange_rate": get_exchange_rate(date, account, account_details.account_currency, company, debit=debit, credit=credit, exchange_rate=exchange_rate) } @@ -797,8 +803,9 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi return grid_values +# Added posting_date as one of the parameters of get_exchange_rate @frappe.whitelist() -def get_exchange_rate(account, account_currency=None, company=None, +def get_exchange_rate(posting_date, account, account_currency=None, company=None, reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None): from erpnext.setup.utils import get_exchange_rate account_details = frappe.db.get_value("Account", account, @@ -824,8 +831,9 @@ def get_exchange_rate(account, account_currency=None, company=None, (account_details.root_type == "Liability" and debit)): exchange_rate = get_average_exchange_rate(account) - if not exchange_rate and account_currency: - exchange_rate = get_exchange_rate(account_currency, company_currency) + if not exchange_rate and account_currency and posting_date: + # The date used to retreive the exchange rate here is the date passed in as an argument to this function. + exchange_rate = get_exchange_rate(posting_date, account_currency, company_currency) else: exchange_rate = 1 diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 219fbf2e73..be12d577f1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -350,6 +350,7 @@ frappe.ui.form.on('Payment Entry', { frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { + posting_date: frm.doc.posting_date, from_currency: from_currency, to_currency: to_currency }, @@ -442,6 +443,7 @@ frappe.ui.form.on('Payment Entry', { method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_outstanding_reference_documents', args: { args: { + "posting_date": frm.doc.posting_date, "company": frm.doc.company, "party_type": frm.doc.party_type, "payment_type": frm.doc.payment_type, diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 179a321d19..314e814021 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -145,11 +145,11 @@ class PaymentEntry(AccountsController): elif self.payment_type in ("Pay", "Internal Transfer"): self.source_exchange_rate = get_average_exchange_rate(self.paid_from) else: - self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency, + self.source_exchange_rate = get_exchange_rate(self.posting_date, self.paid_from_account_currency, self.company_currency) if self.paid_to and not self.target_exchange_rate: - self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency, + self.target_exchange_rate = get_exchange_rate(self.posting_date, self.paid_to_account_currency, self.company_currency) def validate_mandatory(self): @@ -475,12 +475,12 @@ def get_outstanding_reference_documents(args): d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") # Get all SO / PO which are not fully billed or aginst which full advance not paid - orders_to_be_billed = get_orders_to_be_billed(args.get("party_type"), args.get("party"), + orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"), party_account_currency, company_currency) return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed -def get_orders_to_be_billed(party_type, party, party_account_currency, company_currency): +def get_orders_to_be_billed(posting_date, party_type, party, party_account_currency, company_currency): voucher_type = 'Sales Order' if party_type == "Customer" else 'Purchase Order' ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total" @@ -510,7 +510,8 @@ def get_orders_to_be_billed(party_type, party, party_account_currency, company_c order_list = [] for d in orders: d["voucher_type"] = voucher_type - d["exchange_rate"] = get_exchange_rate(party_account_currency, company_currency) + # This assumes that the exchange rate required is the one in the SO + d["exchange_rate"] = get_exchange_rate(posting_date,party_account_currency, company_currency) order_list.append(d) return order_list @@ -585,14 +586,16 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre exchange_rate = 1 else: total_amount = ref_doc.grand_total + # Get the exchange rate from the original ref doc or get it based on the posting date of the ref doc exchange_rate = ref_doc.get("conversion_rate") or \ - get_exchange_rate(party_account_currency, ref_doc.company_currency) + get_exchange_rate(ref_doc.posting_date, party_account_currency, ref_doc.company_currency) outstanding_amount = ref_doc.get("outstanding_amount") \ if reference_doctype in ("Sales Invoice", "Purchase Invoice") \ else flt(total_amount) - flt(ref_doc.advance_paid) else: - exchange_rate = get_exchange_rate(party_account_currency, ref_doc.company_currency) + # Get the exchange rate based on the posting date of the ref doc + exchange_rate = get_exchange_rate(ref_doc.posting_date, party_account_currency, ref_doc.company_currency) return frappe._dict({ "due_date": ref_doc.get("due_date"), diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 73c412f247..d695bf6cc8 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -9,6 +9,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.setup.utils import get_exchange_rate +from frappe.utils import nowdate # test_records = frappe.get_test_records('Payment Request') test_dependencies = ["Currency Exchange", "Journal Entry", "Contact", "Address"] @@ -52,7 +53,7 @@ class TestPaymentRequest(unittest.TestCase): self.assertEquals(pr.reference_name, so_inr.name) self.assertEquals(pr.currency, "INR") - conversion_rate = get_exchange_rate("USD", "INR") + conversion_rate = get_exchange_rate(nowdate(), "USD", "INR") si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate) pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com") diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 5336b5452c..4ac8cec8cf 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -60,7 +60,7 @@ def update_pos_profile_data(doc, pos_profile, company_data): doc.currency = pos_profile.get('currency') or company_data.default_currency doc.conversion_rate = 1.0 if doc.currency != company_data.default_currency: - doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency) + doc.conversion_rate = get_exchange_rate(doc.posting_date, doc.currency, company_data.default_currency) doc.selling_price_list = pos_profile.get('selling_price_list') or \ frappe.db.get_value('Selling Settings', None, 'selling_price_list') doc.naming_series = pos_profile.get('naming_series') or 'SINV-' diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index 1793fc3140..2b024ef385 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from erpnext.setup.utils import get_exchange_rate +from erpnext.utils import nowdate import frappe @@ -39,7 +40,7 @@ def get_quote_list(item, qty_list): #Add a row for each supplier for root in set(suppliers): supplier_currency = frappe.db.get_value("Supplier",root,"default_currency") - exg = get_exchange_rate(supplier_currency,company_currency) + exg = get_exchange_rate(nowdate(),supplier_currency,company_currency) row = frappe._dict({ "supplier_name": root diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index a2a1be2302..bc85f4dbd8 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -202,7 +202,7 @@ def make_quotation(source_name, target_doc=None): if company_currency == quotation.currency: exchange_rate = 1 else: - exchange_rate = get_exchange_rate(quotation.currency, company_currency) + exchange_rate = get_exchange_rate(quotation.transaction_date, quotation.currency, company_currency) quotation.conversion_rate = exchange_rate diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py index ab8ec77257..c7ec62b6f4 100644 --- a/erpnext/demo/user/purchase.py +++ b/erpnext/demo/user/purchase.py @@ -12,6 +12,7 @@ from erpnext.exceptions import InvalidCurrency from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \ make_supplier_quotation as make_quotation_from_rfq +from frappe.utils.nowdate def work(): frappe.set_user(frappe.db.get_global('demo_purchase_user')) @@ -56,7 +57,7 @@ def work(): if company_currency == party_account_currency: exchange_rate = 1 else: - exchange_rate = get_exchange_rate(party_account_currency, company_currency) + exchange_rate = get_exchange_rate(nowdate(), party_account_currency, company_currency) # make supplier quotations if random.random() < 0.2: diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py index 10df14334f..b9e6535e43 100644 --- a/erpnext/demo/user/sales.py +++ b/erpnext/demo/user/sales.py @@ -9,6 +9,7 @@ from frappe.utils.make_random import add_random_children, get_random from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.party import get_party_account_currency from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request, make_payment_entry +from frappe.utils import nowdate def work(): frappe.set_user(frappe.db.get_global('demo_sales_user_2')) @@ -88,7 +89,7 @@ def make_quotation(): if company_currency == party_account_currency: exchange_rate = 1 else: - exchange_rate = get_exchange_rate(party_account_currency, company_currency) + exchange_rate = get_exchange_rate(nowdate(), party_account_currency, company_currency) qtn = frappe.get_doc({ "creation": frappe.flags.current_date, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6a0d0f8b7f..78776a3833 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -473,7 +473,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var company_currency = this.get_company_currency(); // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc if(this.frm.doc.price_list_currency !== company_currency && !this.frm.doc.ignore_pricing_rule) { - this.get_exchange_rate(this.frm.doc.price_list_currency, company_currency, + this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency, function(exchange_rate) { me.frm.set_value("plc_conversion_rate", exchange_rate); }); From 7e74fb4357f02772a1c7345ab95d2942fe8f740a Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Mon, 19 Sep 2016 00:59:25 +0100 Subject: [PATCH 04/14] Modification to set_price_list_currency in accounts_controller.py to allow for specification of date in get_exchange_rate call --- erpnext/controllers/accounts_controller.py | 1321 ++++++++++---------- erpnext/setup/utils.py | 3 +- 2 files changed, 663 insertions(+), 661 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8927da7f78..2f0c4a38a0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -16,705 +16,706 @@ from erpnext.exceptions import InvalidCurrency force_item_fields = ("item_group", "barcode", "brand", "stock_uom") class AccountsController(TransactionBase): - def __init__(self, arg1, arg2=None): - super(AccountsController, self).__init__(arg1, arg2) - - @property - def company_currency(self): - if not hasattr(self, "__company_currency"): - self.__company_currency = get_company_currency(self.company) - - return self.__company_currency - - def validate(self): - if self.get("_action") and self._action != "update_after_submit": - self.set_missing_values(for_validate=True) - self.validate_date_with_fiscal_year() - - if self.meta.get_field("currency"): - self.calculate_taxes_and_totals() - - if not self.meta.get_field("is_return") or not self.is_return: - self.validate_value("base_grand_total", ">=", 0) - - validate_return(self) - self.set_total_in_words() - - if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return: - self.validate_due_date() - self.validate_advance_entries() - - if self.meta.get_field("taxes_and_charges"): - self.validate_enabled_taxes_and_charges() - - self.validate_party() - self.validate_currency() - - if self.meta.get_field("is_recurring"): - if self.amended_from and self.recurring_id: - self.recurring_id = None - if not self.get("__islocal"): - validate_recurring_document(self) - convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) - - if self.doctype == 'Purchase Invoice': - self.validate_paid_amount() - - def validate_paid_amount(self): - if hasattr(self, "is_pos") or hasattr(self, "is_paid"): - is_paid = self.get("is_pos") or self.get("is_paid") - if cint(is_paid) == 1: - if flt(self.paid_amount) == 0 and flt(self.outstanding_amount) > 0: - if self.cash_bank_account: - self.paid_amount = flt(flt(self.grand_total) - flt(self.write_off_amount), - self.precision("paid_amount")) - self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, self.precision("base_paid_amount")) - else: - # show message that the amount is not paid - self.paid_amount = 0 - frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) - else: - frappe.db.set(self,'paid_amount',0) - - def on_update_after_submit(self): - if self.meta.get_field("is_recurring"): - validate_recurring_document(self) - convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) - - def set_missing_values(self, for_validate=False): - if frappe.flags.in_test: - for fieldname in ["posting_date","transaction_date"]: - if self.meta.get_field(fieldname) and not self.get(fieldname): - self.set(fieldname, today()) - break - - def calculate_taxes_and_totals(self): - from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals - calculate_taxes_and_totals(self) - - if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: - self.calculate_commission() - self.calculate_contribution() - - def validate_date_with_fiscal_year(self): - if self.meta.get_field("fiscal_year") : - date_field = "" - if self.meta.get_field("posting_date"): - date_field = "posting_date" - elif self.meta.get_field("transaction_date"): - date_field = "transaction_date" - - if date_field and self.get(date_field): - validate_fiscal_year(self.get(date_field), self.fiscal_year, - self.meta.get_label(date_field), self) - - def validate_due_date(self): - from erpnext.accounts.party import validate_due_date - if self.doctype == "Sales Invoice": - if not self.due_date: - frappe.throw(_("Due Date is mandatory")) - - validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company) - elif self.doctype == "Purchase Invoice": - validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company) - - def set_price_list_currency(self, buying_or_selling): - if self.meta.get_field("currency"): - # price list part - fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ - else "buying_price_list" - if self.meta.get_field(fieldname) and self.get(fieldname): - self.price_list_currency = frappe.db.get_value("Price List", - self.get(fieldname), "currency") - - if self.price_list_currency == self.company_currency: - self.plc_conversion_rate = 1.0 - - elif not self.plc_conversion_rate: - self.plc_conversion_rate = get_exchange_rate( - self.price_list_currency, self.company_currency) - - # currency - if not self.currency: - self.currency = self.price_list_currency - self.conversion_rate = self.plc_conversion_rate - elif self.currency == self.company_currency: - self.conversion_rate = 1.0 - elif not self.conversion_rate: - self.conversion_rate = get_exchange_rate(self.currency, - self.company_currency) - - def set_missing_item_details(self): - """set missing item values""" - from erpnext.stock.get_item_details import get_item_details - - if hasattr(self, "items"): - parent_dict = {} - for fieldname in self.meta.get_valid_columns(): - parent_dict[fieldname] = self.get(fieldname) - - if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: - document_type = "{} Item".format(self.doctype) - parent_dict.update({"document_type": document_type}) - - for item in self.get("items"): - if item.get("item_code"): - args = parent_dict.copy() - args.update(item.as_dict()) - - args["doctype"] = self.doctype - args["name"] = self.name - - if not args.get("transaction_date"): - args["transaction_date"] = args.get("posting_date") - - if self.get("is_subcontracted"): - args["is_subcontracted"] = self.is_subcontracted - - ret = get_item_details(args) - - - for fieldname, value in ret.items(): - if item.meta.get_field(fieldname) and value is not None: - if (item.get(fieldname) is None or fieldname in force_item_fields): - item.set(fieldname, value) - - elif fieldname == "cost_center" and not item.get("cost_center"): - item.set(fieldname, value) - - elif fieldname == "conversion_factor" and not item.get("conversion_factor"): - item.set(fieldname, value) - - if ret.get("pricing_rule"): - # if user changed the discount percentage then set user's discount percentage ? - item.set("discount_percentage", ret.get("discount_percentage")) - if ret.get("pricing_rule_for") == "Price": - item.set("pricing_list_rate", ret.get("pricing_list_rate")) - - if item.price_list_rate: - item.rate = flt(item.price_list_rate * - (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate")) - - if self.doctype == "Purchase Invoice": - self.set_expense_account() - - def set_taxes(self): - if not self.meta.get_field("taxes"): - return - - tax_master_doctype = self.meta.get_field("taxes_and_charges").options - - if not self.get("taxes"): - if not self.get("taxes_and_charges"): - # get the default tax master - self.set("taxes_and_charges", frappe.db.get_value(tax_master_doctype, {"is_default": 1})) - - self.append_taxes_from_master(tax_master_doctype) - - def append_taxes_from_master(self, tax_master_doctype=None): - if self.get("taxes_and_charges"): - if not tax_master_doctype: - tax_master_doctype = self.meta.get_field("taxes_and_charges").options - - self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges"))) - - def set_other_charges(self): - self.set("taxes", []) - self.set_taxes() - - def validate_enabled_taxes_and_charges(self): - taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges") - if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"): - frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges)) - - def get_gl_dict(self, args, account_currency=None): - """this method populates the common properties of a gl entry record""" - - fiscal_years = get_fiscal_years(self.posting_date, company=self.company) - if len(fiscal_years) > 1: - frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(formatdate(self.posting_date))) - else: - fiscal_year = fiscal_years[0][0] - - gl_dict = frappe._dict({ - 'company': self.company, - 'posting_date': self.posting_date, - 'fiscal_year': fiscal_year, - 'voucher_type': self.doctype, - 'voucher_no': self.name, - 'remarks': self.get("remarks"), - 'debit': 0, - 'credit': 0, - 'debit_in_account_currency': 0, - 'credit_in_account_currency': 0, - 'is_opening': self.get("is_opening") or "No", - 'party_type': None, - 'party': None, - 'project': self.get("project") - }) - gl_dict.update(args) - - if not account_currency: - account_currency = get_account_currency(gl_dict.account) - - if self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]: - self.validate_account_currency(gl_dict.account, account_currency) - set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency) - - return gl_dict - - def validate_account_currency(self, account, account_currency=None): - valid_currency = [self.company_currency] - if self.get("currency") and self.currency != self.company_currency: - valid_currency.append(self.currency) - - if account_currency not in valid_currency: - frappe.throw(_("Account {0} is invalid. Account Currency must be {1}") - .format(account, _(" or ").join(valid_currency))) - - def clear_unallocated_advances(self, childtype, parentfield): - self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]})) - - frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s - and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name)) - - def set_advances(self): - """Returns list of advances against Account, Party, Reference""" - - res = self.get_advance_entries() - - self.set("advances", []) - for d in res: - self.append("advances", { - "doctype": self.doctype + " Advance", - "reference_type": d.reference_type, - "reference_name": d.reference_name, - "reference_row": d.reference_row, - "remarks": d.remarks, - "advance_amount": flt(d.amount), - "allocated_amount": flt(d.amount) if d.against_order else 0 - }) - - def get_advance_entries(self, include_unallocated=True): - if self.doctype == "Sales Invoice": - party_account = self.debit_to - party_type = "Customer" - party = self.customer - amount_field = "credit_in_account_currency" - order_field = "sales_order" - order_doctype = "Sales Order" - else: - party_account = self.credit_to - party_type = "Supplier" - party = self.supplier - amount_field = "debit_in_account_currency" - order_field = "purchase_order" - order_doctype = "Purchase Order" - - order_list = list(set([d.get(order_field) - for d in self.get("items") if d.get(order_field)])) - - journal_entries = get_advance_journal_entries(party_type, party, party_account, - amount_field, order_doctype, order_list, include_unallocated) - - payment_entries = get_advance_payment_entries(party_type, party, party_account, - order_doctype, order_list, include_unallocated) - - res = journal_entries + payment_entries - - return res - - def validate_advance_entries(self): - order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" - order_list = list(set([d.get(order_field) - for d in self.get("items") if d.get(order_field)])) - - if not order_list: return - - advance_entries = self.get_advance_entries(include_unallocated=False) - - if advance_entries: - advance_entries_against_si = [d.reference_name for d in self.get("advances")] - for d in advance_entries: - if not advance_entries_against_si or d.reference_name not in advance_entries_against_si: - frappe.msgprint(_("Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.") - .format(d.reference_name, d.against_order)) - - def update_against_document_in_jv(self): - """ - Links invoice and advance voucher: - 1. cancel advance voucher - 2. split into multiple rows if partially adjusted, assign against voucher - 3. submit advance voucher - """ - - if self.doctype == "Sales Invoice": - party_type = "Customer" - party = self.customer - party_account = self.debit_to - dr_or_cr = "credit_in_account_currency" - else: - party_type = "Supplier" - party = self.supplier - party_account = self.credit_to - dr_or_cr = "debit_in_account_currency" - - lst = [] - for d in self.get('advances'): - if flt(d.allocated_amount) > 0: - args = frappe._dict({ - 'voucher_type': d.reference_type, - 'voucher_no' : d.reference_name, - 'voucher_detail_no' : d.reference_row, - 'against_voucher_type' : self.doctype, - 'against_voucher' : self.name, - 'account' : party_account, - 'party_type': party_type, - 'party': party, - 'is_advance' : 'Yes', - 'dr_or_cr' : dr_or_cr, - 'unadjusted_amount' : flt(d.advance_amount), - 'allocated_amount' : flt(d.allocated_amount), - 'exchange_rate': (self.conversion_rate - if self.party_account_currency != self.company_currency else 1), - 'grand_total': (self.base_grand_total - if self.party_account_currency==self.company_currency else self.grand_total), - 'outstanding_amount': self.outstanding_amount - }) - lst.append(args) - - if lst: - from erpnext.accounts.utils import reconcile_against_document - reconcile_against_document(lst) - - def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): - from erpnext.controllers.status_updater import get_tolerance_for - item_tolerance = {} - global_tolerance = None - - for item in self.get("items"): - if item.get(item_ref_dn): - ref_amt = flt(frappe.db.get_value(ref_dt + " Item", - item.get(item_ref_dn), based_on), self.precision(based_on, item)) - if not ref_amt: - frappe.msgprint(_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero").format(item.item_code, ref_dt)) - else: - already_billed = frappe.db.sql("""select sum(%s) from `tab%s` - where %s=%s and docstatus=1 and parent != %s""" % - (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] - - total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), - self.precision(based_on, item)) - - tolerance, item_tolerance, global_tolerance = get_tolerance_for(item.item_code, - item_tolerance, global_tolerance) - - max_allowed_amt = flt(ref_amt * (100 + tolerance) / 100) - - if total_billed_amt - max_allowed_amt > 0.01: - frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow overbilling, please set in Stock Settings").format(item.item_code, item.idx, max_allowed_amt)) - - def get_company_default(self, fieldname): - from erpnext.accounts.utils import get_company_default - return get_company_default(self.company, fieldname) - - def get_stock_items(self): - stock_items = [] - item_codes = list(set(item.item_code for item in self.get("items"))) - if item_codes: - stock_items = [r[0] for r in frappe.db.sql("""select name - from `tabItem` where name in (%s) and is_stock_item=1""" % \ - (", ".join((["%s"]*len(item_codes))),), item_codes)] - - return stock_items - - def set_total_advance_paid(self): - if self.doctype == "Sales Order": - dr_or_cr = "credit_in_account_currency" - party = self.customer - else: - dr_or_cr = "debit_in_account_currency" - party = self.supplier - - advance = frappe.db.sql(""" - select - account_currency, sum({dr_or_cr}) as amount - from - `tabGL Entry` - where - against_voucher_type = %s and against_voucher = %s and party=%s - and docstatus = 1 - """.format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1) - - if advance: - advance = advance[0] - advance_paid = flt(advance.amount, self.precision("advance_paid")) - formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"), - currency=advance.account_currency) - - frappe.db.set_value(self.doctype, self.name, "party_account_currency", - advance.account_currency) - - if advance.account_currency == self.currency: - order_total = self.grand_total - formatted_order_total = fmt_money(order_total, precision=self.precision("grand_total"), - currency=advance.account_currency) - else: - order_total = self.base_grand_total - formatted_order_total = fmt_money(order_total, precision=self.precision("base_grand_total"), - currency=advance.account_currency) - - if self.currency == self.company_currency and advance_paid > order_total: - frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})") - .format(formatted_advance_paid, self.name, formatted_order_total)) - - frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid) - - @property - def company_abbr(self): - if not hasattr(self, "_abbr"): - self._abbr = frappe.db.get_value("Company", self.company, "abbr") - - return self._abbr - - def validate_party(self): - party_type, party = self.get_party() - validate_party_frozen_disabled(party_type, party) - - def get_party(self): - party_type = None - if self.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"): - party_type = 'Customer' - - elif self.doctype in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"): - party_type = 'Supplier' - - elif self.meta.get_field("customer"): - party_type = "Customer" - - elif self.meta.get_field("supplier"): - party_type = "Supplier" - - party = self.get(party_type.lower()) if party_type else None - - return party_type, party - - def validate_currency(self): - if self.get("currency"): - party_type, party = self.get_party() - if party_type and party: - party_account_currency = get_party_account_currency(party_type, party, self.company) - - if (party_account_currency - and party_account_currency != self.company_currency - and self.currency != party_account_currency): - - frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") - .format(party_type, party, party_account_currency), InvalidCurrency) - - # Note: not validating with gle account because we don't have the account - # at quotation / sales order level and we shouldn't stop someone - # from creating a sales invoice if sales order is already created - - def validate_fixed_asset(self): - for d in self.get("items"): - if d.is_fixed_asset: - if d.qty > 1: - frappe.throw(_("Row #{0}: Qty must be 1, as item is a fixed asset. Please use separate row for multiple qty.").format(d.idx)) - - if d.meta.get_field("asset"): - if not d.asset: - frappe.throw(_("Row #{0}: Asset is mandatory for fixed asset purchase/sale") - .format(d.idx)) - else: - asset = frappe.get_doc("Asset", d.asset) - - if asset.company != self.company: - frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}") - .format(d.idx, d.asset, self.company)) - - elif asset.item_code != d.item_code: - frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}") - .format(d.idx, d.asset, d.item_code)) - - elif asset.docstatus != 1: - frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset)) - - elif self.doctype == "Purchase Invoice": - if asset.status != "Submitted": - frappe.throw(_("Row #{0}: Asset {1} is already {2}") - .format(d.idx, d.asset, asset.status)) - elif getdate(asset.purchase_date) != getdate(self.posting_date): - frappe.throw(_("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx, asset.purchase_date, d.asset)) - elif asset.is_existing_asset: - frappe.throw(_("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format(d.idx, d.asset)) - - elif self.docstatus=="Sales Invoice" and self.docstatus == 1: - if self.update_stock: - frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) - - elif asset.status in ("Scrapped", "Cancelled", "Sold"): - frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}") - .format(d.idx, d.asset, asset.status)) + def __init__(self, arg1, arg2=None): + super(AccountsController, self).__init__(arg1, arg2) + + @property + def company_currency(self): + if not hasattr(self, "__company_currency"): + self.__company_currency = get_company_currency(self.company) + + return self.__company_currency + + def validate(self): + if self.get("_action") and self._action != "update_after_submit": + self.set_missing_values(for_validate=True) + self.validate_date_with_fiscal_year() + + if self.meta.get_field("currency"): + self.calculate_taxes_and_totals() + + if not self.meta.get_field("is_return") or not self.is_return: + self.validate_value("base_grand_total", ">=", 0) + + validate_return(self) + self.set_total_in_words() + + if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return: + self.validate_due_date() + self.validate_advance_entries() + + if self.meta.get_field("taxes_and_charges"): + self.validate_enabled_taxes_and_charges() + + self.validate_party() + self.validate_currency() + + if self.meta.get_field("is_recurring"): + if self.amended_from and self.recurring_id: + self.recurring_id = None + if not self.get("__islocal"): + validate_recurring_document(self) + convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) + + if self.doctype == 'Purchase Invoice': + self.validate_paid_amount() + + def validate_paid_amount(self): + if hasattr(self, "is_pos") or hasattr(self, "is_paid"): + is_paid = self.get("is_pos") or self.get("is_paid") + if cint(is_paid) == 1: + if flt(self.paid_amount) == 0 and flt(self.outstanding_amount) > 0: + if self.cash_bank_account: + self.paid_amount = flt(flt(self.grand_total) - flt(self.write_off_amount), + self.precision("paid_amount")) + self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, self.precision("base_paid_amount")) + else: + # show message that the amount is not paid + self.paid_amount = 0 + frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) + else: + frappe.db.set(self,'paid_amount',0) + + def on_update_after_submit(self): + if self.meta.get_field("is_recurring"): + validate_recurring_document(self) + convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) + + def set_missing_values(self, for_validate=False): + if frappe.flags.in_test: + for fieldname in ["posting_date","transaction_date"]: + if self.meta.get_field(fieldname) and not self.get(fieldname): + self.set(fieldname, today()) + break + + def calculate_taxes_and_totals(self): + from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals + calculate_taxes_and_totals(self) + + if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: + self.calculate_commission() + self.calculate_contribution() + + def validate_date_with_fiscal_year(self): + if self.meta.get_field("fiscal_year") : + date_field = "" + if self.meta.get_field("posting_date"): + date_field = "posting_date" + elif self.meta.get_field("transaction_date"): + date_field = "transaction_date" + + if date_field and self.get(date_field): + validate_fiscal_year(self.get(date_field), self.fiscal_year, + self.meta.get_label(date_field), self) + + def validate_due_date(self): + from erpnext.accounts.party import validate_due_date + if self.doctype == "Sales Invoice": + if not self.due_date: + frappe.throw(_("Due Date is mandatory")) + + validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company) + elif self.doctype == "Purchase Invoice": + validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company) + + def set_price_list_currency(self, buying_or_selling): + if self.meta.get_field("currency"): + # price list part + fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ + else "buying_price_list" + if self.meta.get_field(fieldname) and self.get(fieldname): + self.price_list_currency = frappe.db.get_value("Price List", + self.get(fieldname), "currency") + + if self.price_list_currency == self.company_currency: + self.plc_conversion_rate = 1.0 + + elif not self.plc_conversion_rate: + # manqala 19/09/16: using the transaction date on the PO or SO as the basis for getting the exchange rate + self.plc_conversion_rate = get_exchange_rate( + self.transaction_date, self.price_list_currency, self.company_currency) + + # currency + if not self.currency: + self.currency = self.price_list_currency + self.conversion_rate = self.plc_conversion_rate + elif self.currency == self.company_currency: + self.conversion_rate = 1.0 + elif not self.conversion_rate: + self.conversion_rate = get_exchange_rate(self.currency, + self.company_currency) + + def set_missing_item_details(self): + """set missing item values""" + from erpnext.stock.get_item_details import get_item_details + + if hasattr(self, "items"): + parent_dict = {} + for fieldname in self.meta.get_valid_columns(): + parent_dict[fieldname] = self.get(fieldname) + + if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: + document_type = "{} Item".format(self.doctype) + parent_dict.update({"document_type": document_type}) + + for item in self.get("items"): + if item.get("item_code"): + args = parent_dict.copy() + args.update(item.as_dict()) + + args["doctype"] = self.doctype + args["name"] = self.name + + if not args.get("transaction_date"): + args["transaction_date"] = args.get("posting_date") + + if self.get("is_subcontracted"): + args["is_subcontracted"] = self.is_subcontracted + + ret = get_item_details(args) + + + for fieldname, value in ret.items(): + if item.meta.get_field(fieldname) and value is not None: + if (item.get(fieldname) is None or fieldname in force_item_fields): + item.set(fieldname, value) + + elif fieldname == "cost_center" and not item.get("cost_center"): + item.set(fieldname, value) + + elif fieldname == "conversion_factor" and not item.get("conversion_factor"): + item.set(fieldname, value) + + if ret.get("pricing_rule"): + # if user changed the discount percentage then set user's discount percentage ? + item.set("discount_percentage", ret.get("discount_percentage")) + if ret.get("pricing_rule_for") == "Price": + item.set("pricing_list_rate", ret.get("pricing_list_rate")) + + if item.price_list_rate: + item.rate = flt(item.price_list_rate * + (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate")) + + if self.doctype == "Purchase Invoice": + self.set_expense_account() + + def set_taxes(self): + if not self.meta.get_field("taxes"): + return + + tax_master_doctype = self.meta.get_field("taxes_and_charges").options + + if not self.get("taxes"): + if not self.get("taxes_and_charges"): + # get the default tax master + self.set("taxes_and_charges", frappe.db.get_value(tax_master_doctype, {"is_default": 1})) + + self.append_taxes_from_master(tax_master_doctype) + + def append_taxes_from_master(self, tax_master_doctype=None): + if self.get("taxes_and_charges"): + if not tax_master_doctype: + tax_master_doctype = self.meta.get_field("taxes_and_charges").options + + self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges"))) + + def set_other_charges(self): + self.set("taxes", []) + self.set_taxes() + + def validate_enabled_taxes_and_charges(self): + taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges") + if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"): + frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges)) + + def get_gl_dict(self, args, account_currency=None): + """this method populates the common properties of a gl entry record""" + + fiscal_years = get_fiscal_years(self.posting_date, company=self.company) + if len(fiscal_years) > 1: + frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(formatdate(self.posting_date))) + else: + fiscal_year = fiscal_years[0][0] + + gl_dict = frappe._dict({ + 'company': self.company, + 'posting_date': self.posting_date, + 'fiscal_year': fiscal_year, + 'voucher_type': self.doctype, + 'voucher_no': self.name, + 'remarks': self.get("remarks"), + 'debit': 0, + 'credit': 0, + 'debit_in_account_currency': 0, + 'credit_in_account_currency': 0, + 'is_opening': self.get("is_opening") or "No", + 'party_type': None, + 'party': None, + 'project': self.get("project") + }) + gl_dict.update(args) + + if not account_currency: + account_currency = get_account_currency(gl_dict.account) + + if self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]: + self.validate_account_currency(gl_dict.account, account_currency) + set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency) + + return gl_dict + + def validate_account_currency(self, account, account_currency=None): + valid_currency = [self.company_currency] + if self.get("currency") and self.currency != self.company_currency: + valid_currency.append(self.currency) + + if account_currency not in valid_currency: + frappe.throw(_("Account {0} is invalid. Account Currency must be {1}") + .format(account, _(" or ").join(valid_currency))) + + def clear_unallocated_advances(self, childtype, parentfield): + self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]})) + + frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s + and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name)) + + def set_advances(self): + """Returns list of advances against Account, Party, Reference""" + + res = self.get_advance_entries() + + self.set("advances", []) + for d in res: + self.append("advances", { + "doctype": self.doctype + " Advance", + "reference_type": d.reference_type, + "reference_name": d.reference_name, + "reference_row": d.reference_row, + "remarks": d.remarks, + "advance_amount": flt(d.amount), + "allocated_amount": flt(d.amount) if d.against_order else 0 + }) + + def get_advance_entries(self, include_unallocated=True): + if self.doctype == "Sales Invoice": + party_account = self.debit_to + party_type = "Customer" + party = self.customer + amount_field = "credit_in_account_currency" + order_field = "sales_order" + order_doctype = "Sales Order" + else: + party_account = self.credit_to + party_type = "Supplier" + party = self.supplier + amount_field = "debit_in_account_currency" + order_field = "purchase_order" + order_doctype = "Purchase Order" + + order_list = list(set([d.get(order_field) + for d in self.get("items") if d.get(order_field)])) + + journal_entries = get_advance_journal_entries(party_type, party, party_account, + amount_field, order_doctype, order_list, include_unallocated) + + payment_entries = get_advance_payment_entries(party_type, party, party_account, + order_doctype, order_list, include_unallocated) + + res = journal_entries + payment_entries + + return res + + def validate_advance_entries(self): + order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" + order_list = list(set([d.get(order_field) + for d in self.get("items") if d.get(order_field)])) + + if not order_list: return + + advance_entries = self.get_advance_entries(include_unallocated=False) + + if advance_entries: + advance_entries_against_si = [d.reference_name for d in self.get("advances")] + for d in advance_entries: + if not advance_entries_against_si or d.reference_name not in advance_entries_against_si: + frappe.msgprint(_("Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.") + .format(d.reference_name, d.against_order)) + + def update_against_document_in_jv(self): + """ + Links invoice and advance voucher: + 1. cancel advance voucher + 2. split into multiple rows if partially adjusted, assign against voucher + 3. submit advance voucher + """ + + if self.doctype == "Sales Invoice": + party_type = "Customer" + party = self.customer + party_account = self.debit_to + dr_or_cr = "credit_in_account_currency" + else: + party_type = "Supplier" + party = self.supplier + party_account = self.credit_to + dr_or_cr = "debit_in_account_currency" + + lst = [] + for d in self.get('advances'): + if flt(d.allocated_amount) > 0: + args = frappe._dict({ + 'voucher_type': d.reference_type, + 'voucher_no' : d.reference_name, + 'voucher_detail_no' : d.reference_row, + 'against_voucher_type' : self.doctype, + 'against_voucher' : self.name, + 'account' : party_account, + 'party_type': party_type, + 'party': party, + 'is_advance' : 'Yes', + 'dr_or_cr' : dr_or_cr, + 'unadjusted_amount' : flt(d.advance_amount), + 'allocated_amount' : flt(d.allocated_amount), + 'exchange_rate': (self.conversion_rate + if self.party_account_currency != self.company_currency else 1), + 'grand_total': (self.base_grand_total + if self.party_account_currency==self.company_currency else self.grand_total), + 'outstanding_amount': self.outstanding_amount + }) + lst.append(args) + + if lst: + from erpnext.accounts.utils import reconcile_against_document + reconcile_against_document(lst) + + def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): + from erpnext.controllers.status_updater import get_tolerance_for + item_tolerance = {} + global_tolerance = None + + for item in self.get("items"): + if item.get(item_ref_dn): + ref_amt = flt(frappe.db.get_value(ref_dt + " Item", + item.get(item_ref_dn), based_on), self.precision(based_on, item)) + if not ref_amt: + frappe.msgprint(_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero").format(item.item_code, ref_dt)) + else: + already_billed = frappe.db.sql("""select sum(%s) from `tab%s` + where %s=%s and docstatus=1 and parent != %s""" % + (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), + (item.get(item_ref_dn), self.name))[0][0] + + total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), + self.precision(based_on, item)) + + tolerance, item_tolerance, global_tolerance = get_tolerance_for(item.item_code, + item_tolerance, global_tolerance) + + max_allowed_amt = flt(ref_amt * (100 + tolerance) / 100) + + if total_billed_amt - max_allowed_amt > 0.01: + frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow overbilling, please set in Stock Settings").format(item.item_code, item.idx, max_allowed_amt)) + + def get_company_default(self, fieldname): + from erpnext.accounts.utils import get_company_default + return get_company_default(self.company, fieldname) + + def get_stock_items(self): + stock_items = [] + item_codes = list(set(item.item_code for item in self.get("items"))) + if item_codes: + stock_items = [r[0] for r in frappe.db.sql("""select name + from `tabItem` where name in (%s) and is_stock_item=1""" % \ + (", ".join((["%s"]*len(item_codes))),), item_codes)] + + return stock_items + + def set_total_advance_paid(self): + if self.doctype == "Sales Order": + dr_or_cr = "credit_in_account_currency" + party = self.customer + else: + dr_or_cr = "debit_in_account_currency" + party = self.supplier + + advance = frappe.db.sql(""" + select + account_currency, sum({dr_or_cr}) as amount + from + `tabGL Entry` + where + against_voucher_type = %s and against_voucher = %s and party=%s + and docstatus = 1 + """.format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1) + + if advance: + advance = advance[0] + advance_paid = flt(advance.amount, self.precision("advance_paid")) + formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"), + currency=advance.account_currency) + + frappe.db.set_value(self.doctype, self.name, "party_account_currency", + advance.account_currency) + + if advance.account_currency == self.currency: + order_total = self.grand_total + formatted_order_total = fmt_money(order_total, precision=self.precision("grand_total"), + currency=advance.account_currency) + else: + order_total = self.base_grand_total + formatted_order_total = fmt_money(order_total, precision=self.precision("base_grand_total"), + currency=advance.account_currency) + + if self.currency == self.company_currency and advance_paid > order_total: + frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})") + .format(formatted_advance_paid, self.name, formatted_order_total)) + + frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid) + + @property + def company_abbr(self): + if not hasattr(self, "_abbr"): + self._abbr = frappe.db.get_value("Company", self.company, "abbr") + + return self._abbr + + def validate_party(self): + party_type, party = self.get_party() + validate_party_frozen_disabled(party_type, party) + + def get_party(self): + party_type = None + if self.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"): + party_type = 'Customer' + + elif self.doctype in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"): + party_type = 'Supplier' + + elif self.meta.get_field("customer"): + party_type = "Customer" + + elif self.meta.get_field("supplier"): + party_type = "Supplier" + + party = self.get(party_type.lower()) if party_type else None + + return party_type, party + + def validate_currency(self): + if self.get("currency"): + party_type, party = self.get_party() + if party_type and party: + party_account_currency = get_party_account_currency(party_type, party, self.company) + + if (party_account_currency + and party_account_currency != self.company_currency + and self.currency != party_account_currency): + + frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") + .format(party_type, party, party_account_currency), InvalidCurrency) + + # Note: not validating with gle account because we don't have the account + # at quotation / sales order level and we shouldn't stop someone + # from creating a sales invoice if sales order is already created + + def validate_fixed_asset(self): + for d in self.get("items"): + if d.is_fixed_asset: + if d.qty > 1: + frappe.throw(_("Row #{0}: Qty must be 1, as item is a fixed asset. Please use separate row for multiple qty.").format(d.idx)) + + if d.meta.get_field("asset"): + if not d.asset: + frappe.throw(_("Row #{0}: Asset is mandatory for fixed asset purchase/sale") + .format(d.idx)) + else: + asset = frappe.get_doc("Asset", d.asset) + + if asset.company != self.company: + frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}") + .format(d.idx, d.asset, self.company)) + + elif asset.item_code != d.item_code: + frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}") + .format(d.idx, d.asset, d.item_code)) + + elif asset.docstatus != 1: + frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset)) + + elif self.doctype == "Purchase Invoice": + if asset.status != "Submitted": + frappe.throw(_("Row #{0}: Asset {1} is already {2}") + .format(d.idx, d.asset, asset.status)) + elif getdate(asset.purchase_date) != getdate(self.posting_date): + frappe.throw(_("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx, asset.purchase_date, d.asset)) + elif asset.is_existing_asset: + frappe.throw(_("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format(d.idx, d.asset)) + + elif self.docstatus=="Sales Invoice" and self.docstatus == 1: + if self.update_stock: + frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) + + elif asset.status in ("Scrapped", "Cancelled", "Sold"): + frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}") + .format(d.idx, d.asset, asset.status)) @frappe.whitelist() def get_tax_rate(account_head): - return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) + return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) @frappe.whitelist() def get_default_taxes_and_charges(master_doctype): - default_tax = frappe.db.get_value(master_doctype, {"is_default": 1}) - return get_taxes_and_charges(master_doctype, default_tax) + default_tax = frappe.db.get_value(master_doctype, {"is_default": 1}) + return get_taxes_and_charges(master_doctype, default_tax) @frappe.whitelist() def get_taxes_and_charges(master_doctype, master_name): - if not master_name: - return - from frappe.model import default_fields - tax_master = frappe.get_doc(master_doctype, master_name) + if not master_name: + return + from frappe.model import default_fields + tax_master = frappe.get_doc(master_doctype, master_name) - taxes_and_charges = [] - for i, tax in enumerate(tax_master.get("taxes")): - tax = tax.as_dict() + taxes_and_charges = [] + for i, tax in enumerate(tax_master.get("taxes")): + tax = tax.as_dict() - for fieldname in default_fields: - if fieldname in tax: - del tax[fieldname] + for fieldname in default_fields: + if fieldname in tax: + del tax[fieldname] - taxes_and_charges.append(tax) + taxes_and_charges.append(tax) - return taxes_and_charges + return taxes_and_charges def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company): - """common validation for currency and price list currency""" + """common validation for currency and price list currency""" - company_currency = frappe.db.get_value("Company", company, "default_currency", cache=True) + company_currency = frappe.db.get_value("Company", company, "default_currency", cache=True) - if not conversion_rate: - throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format( - conversion_rate_label, currency, company_currency)) + if not conversion_rate: + throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format( + conversion_rate_label, currency, company_currency)) def validate_taxes_and_charges(tax): - if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id: - frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'")) - elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']: - if cint(tax.idx) == 1: - frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) - elif not tax.row_id: - frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}".format(tax.idx, _(tax.doctype)))) - elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): - frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) + if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id: + frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'")) + elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']: + if cint(tax.idx) == 1: + frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) + elif not tax.row_id: + frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}".format(tax.idx, _(tax.doctype)))) + elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): + frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) - if tax.charge_type == "Actual": - tax.rate = None + if tax.charge_type == "Actual": + tax.rate = None def validate_inclusive_tax(tax, doc): - def _on_previous_row_error(row_range): - throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, - row_range)) + def _on_previous_row_error(row_range): + throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, + row_range)) - if cint(getattr(tax, "included_in_print_rate", None)): - if tax.charge_type == "Actual": - # inclusive tax cannot be of type Actual - throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx)) - elif tax.charge_type == "On Previous Row Amount" and \ - not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate): - # referred row should also be inclusive - _on_previous_row_error(tax.row_id) - elif tax.charge_type == "On Previous Row Total" and \ - not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]): - # all rows about the reffered tax should be inclusive - _on_previous_row_error("1 - %d" % (tax.row_id,)) - elif tax.get("category") == "Valuation": - frappe.throw(_("Valuation type charges can not marked as Inclusive")) + if cint(getattr(tax, "included_in_print_rate", None)): + if tax.charge_type == "Actual": + # inclusive tax cannot be of type Actual + throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx)) + elif tax.charge_type == "On Previous Row Amount" and \ + not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate): + # referred row should also be inclusive + _on_previous_row_error(tax.row_id) + elif tax.charge_type == "On Previous Row Total" and \ + not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]): + # all rows about the reffered tax should be inclusive + _on_previous_row_error("1 - %d" % (tax.row_id,)) + elif tax.get("category") == "Valuation": + frappe.throw(_("Valuation type charges can not marked as Inclusive")) def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): - if (not conversion_rate) and (account_currency!=company_currency): - frappe.throw(_("Account: {0} with currency: {1} can not be selected") - .format(gl_dict.account, account_currency)) + if (not conversion_rate) and (account_currency!=company_currency): + frappe.throw(_("Account: {0} with currency: {1} can not be selected") + .format(gl_dict.account, account_currency)) - gl_dict["account_currency"] = company_currency if account_currency==company_currency \ - else account_currency + gl_dict["account_currency"] = company_currency if account_currency==company_currency \ + else account_currency - # set debit/credit in account currency if not provided - if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency): - gl_dict.debit_in_account_currency = gl_dict.debit if account_currency==company_currency \ - else flt(gl_dict.debit / conversion_rate, 2) + # set debit/credit in account currency if not provided + if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency): + gl_dict.debit_in_account_currency = gl_dict.debit if account_currency==company_currency \ + else flt(gl_dict.debit / conversion_rate, 2) - if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency): - gl_dict.credit_in_account_currency = gl_dict.credit if account_currency==company_currency \ - else flt(gl_dict.credit / conversion_rate, 2) + if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency): + gl_dict.credit_in_account_currency = gl_dict.credit if account_currency==company_currency \ + else flt(gl_dict.credit / conversion_rate, 2) def get_advance_journal_entries(party_type, party, party_account, amount_field, - order_doctype, order_list, include_unallocated=True): + order_doctype, order_list, include_unallocated=True): - dr_or_cr = "credit_in_account_currency" if party_type=="Customer" else "debit_in_account_currency" + dr_or_cr = "credit_in_account_currency" if party_type=="Customer" else "debit_in_account_currency" - conditions = [] - if include_unallocated: - conditions.append("ifnull(t2.reference_name, '')=''") + conditions = [] + if include_unallocated: + conditions.append("ifnull(t2.reference_name, '')=''") - if order_list: - order_condition = ', '.join(['%s'] * len(order_list)) - conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))"\ - .format(order_doctype, order_condition)) + if order_list: + order_condition = ', '.join(['%s'] * len(order_list)) + conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))"\ + .format(order_doctype, order_condition)) - reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else "" - - journal_entries = frappe.db.sql(""" - select - "Journal Entry" as reference_type, t1.name as reference_name, - t1.remark as remarks, t2.{0} as amount, t2.name as reference_row, - t2.reference_name as against_order - from - `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where - t1.name = t2.parent and t2.account = %s - and t2.party_type = %s and t2.party = %s - and t2.is_advance = 'Yes' and t1.docstatus = 1 - and {1} > 0 {2} - order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition), - [party_account, party_type, party] + order_list, as_dict=1) + reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else "" + + journal_entries = frappe.db.sql(""" + select + "Journal Entry" as reference_type, t1.name as reference_name, + t1.remark as remarks, t2.{0} as amount, t2.name as reference_row, + t2.reference_name as against_order + from + `tabJournal Entry` t1, `tabJournal Entry Account` t2 + where + t1.name = t2.parent and t2.account = %s + and t2.party_type = %s and t2.party = %s + and t2.is_advance = 'Yes' and t1.docstatus = 1 + and {1} > 0 {2} + order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition), + [party_account, party_type, party] + order_list, as_dict=1) - return list(journal_entries) + return list(journal_entries) def get_advance_payment_entries(party_type, party, party_account, - order_doctype, order_list=None, include_unallocated=True, against_all_orders=False): - party_account_field = "paid_from" if party_type == "Customer" else "paid_to" - payment_type = "Receive" if party_type == "Customer" else "Pay" - payment_entries_against_order, unallocated_payment_entries = [], [] + order_doctype, order_list=None, include_unallocated=True, against_all_orders=False): + party_account_field = "paid_from" if party_type == "Customer" else "paid_to" + payment_type = "Receive" if party_type == "Customer" else "Pay" + payment_entries_against_order, unallocated_payment_entries = [], [] - if order_list or against_all_orders: - if order_list: - reference_condition = " and t2.reference_name in ({0})"\ - .format(', '.join(['%s'] * len(order_list))) - else: - reference_condition = "" - order_list = [] + if order_list or against_all_orders: + if order_list: + reference_condition = " and t2.reference_name in ({0})"\ + .format(', '.join(['%s'] * len(order_list))) + else: + reference_condition = "" + order_list = [] - payment_entries_against_order = frappe.db.sql(""" - select - "Payment Entry" as reference_type, t1.name as reference_name, - t1.remarks, t2.allocated_amount as amount, t2.name as reference_row, - t2.reference_name as against_order, t1.posting_date - from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 - where - t1.name = t2.parent and t1.{0} = %s and t1.payment_type = %s - and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 - and t2.reference_doctype = %s {1} - """.format(party_account_field, reference_condition), - [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) + payment_entries_against_order = frappe.db.sql(""" + select + "Payment Entry" as reference_type, t1.name as reference_name, + t1.remarks, t2.allocated_amount as amount, t2.name as reference_row, + t2.reference_name as against_order, t1.posting_date + from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 + where + t1.name = t2.parent and t1.{0} = %s and t1.payment_type = %s + and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 + and t2.reference_doctype = %s {1} + """.format(party_account_field, reference_condition), + [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) - if include_unallocated: - unallocated_payment_entries = frappe.db.sql(""" - select "Payment Entry" as reference_type, name as reference_name, - remarks, unallocated_amount as amount - from `tabPayment Entry` - where - {0} = %s and party_type = %s and party = %s and payment_type = %s - and docstatus = 1 and unallocated_amount > 0 - """.format(party_account_field), (party_account, party_type, party, payment_type), as_dict=1) + if include_unallocated: + unallocated_payment_entries = frappe.db.sql(""" + select "Payment Entry" as reference_type, name as reference_name, + remarks, unallocated_amount as amount + from `tabPayment Entry` + where + {0} = %s and party_type = %s and party = %s and payment_type = %s + and docstatus = 1 and unallocated_amount > 0 + """.format(party_account_field), (party_account, party_type, party, payment_type), as_dict=1) - return list(payment_entries_against_order) + list(unallocated_payment_entries) \ No newline at end of file + return list(payment_entries_against_order) + list(unallocated_payment_entries) \ No newline at end of file diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index c340cac20a..e18a9931a2 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -67,12 +67,13 @@ def before_tests(): @frappe.whitelist() def get_exchange_rate(posting_date, from_currency, to_currency): if not (posting_date and from_currency and to_currency): + # cksgb 19/09/2016: Should this be an empty return or should it throw and exception? return if from_currency == to_currency: return 1 - #Get all entries in Currency Exchange with from_currency and to_currency + # cksgb 19/09/2016: get all entries in Currency Exchange with from_currency and to_currency. Order by date desc. Top one is the required exchange rate 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) From 1f64f9897c430961af412c5d07dcace9a60845f2 Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Mon, 19 Sep 2016 01:34:49 +0100 Subject: [PATCH 05/14] Additional corrections for cases where get_exchange_rate was not called with the correct number of arguments. --- erpnext/controllers/accounts_controller.py | 3 ++- erpnext/stock/get_item_details.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2f0c4a38a0..4392a0a6d3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -142,7 +142,8 @@ class AccountsController(TransactionBase): elif self.currency == self.company_currency: self.conversion_rate = 1.0 elif not self.conversion_rate: - self.conversion_rate = get_exchange_rate(self.currency, + # cksgb 19/09/2016: added transaction date to arguments for get_exchange_rate + self.conversion_rate = get_exchange_rate(self.transaction_date, self.currency, self.company_currency) def set_missing_item_details(self): diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1e3bd8a209..414b2005cf 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -468,7 +468,8 @@ def get_price_list_currency_and_exchange_rate(args): if (not plc_conversion_rate) or (price_list_currency and args.price_list_currency \ and price_list_currency != args.price_list_currency): - plc_conversion_rate = get_exchange_rate(price_list_currency, args.currency) or plc_conversion_rate + # cksgb 19/09/2016: added args.transaction_date as posting_date argument for get_exchange_rate + plc_conversion_rate = get_exchange_rate(args.transaction_date, price_list_currency, args.currency) or plc_conversion_rate return frappe._dict({ "price_list_currency": price_list_currency, From 375a3c003dd26053d34b1775587035cdd0a33c62 Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Mon, 19 Sep 2016 02:35:32 +0100 Subject: [PATCH 06/14] Update to erpnext/public/js/controllers/transaction.js in order to enable it detect when the doctype for which exchange rate is to be determined is a Sales Order or Purchase order. If it is either, then use transaction_date instead of posting_date as the argument to get_exchange_rate method --- erpnext/public/js/controllers/transaction.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 78776a3833..274e295bbb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -417,6 +417,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, currency: function() { + /* cksgb 19/09/2016: We need to detect if this transaction is a PO/SO. + If so, use transaction_date. + If not, use posting_date as we assume that any other document type is an accounting document. */ + + if (this.frm.doc.doctype == "Purchase Order" || this.frm.doc.doctype == "Sales Order"){ + lookup_date = this.frm.doc.transaction_date; + }else{ + lookup_date = this.frm.doc.posting_date; + } + var me = this; this.set_dynamic_labels(); @@ -424,7 +434,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.posting_date, this.frm.doc.currency, company_currency, + this.get_exchange_rate(lookup_date, this.frm.doc.currency, company_currency, function(exchange_rate) { me.frm.set_value("conversion_rate", exchange_rate); }); From 256ec170d01b13119c175d72050ff5542ab59d8c Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Mon, 19 Sep 2016 03:53:37 +0100 Subject: [PATCH 07/14] Corrections properly distinguish between transaction_date and posting_date as the basis for determining translation date that is passed to get_exchange_rate --- erpnext/controllers/accounts_controller.py | 14 +++++++++++--- erpnext/public/js/controllers/transaction.js | 18 ++++++------------ erpnext/setup/utils.py | 10 +++++----- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4392a0a6d3..02e5411630 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -119,6 +119,13 @@ class AccountsController(TransactionBase): validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company) def set_price_list_currency(self, buying_or_selling): + # manqala 19/09/2016: for PO and SO the translation date for exchange rate conversions is transaction_date. For everything else it is posting_date + if self.meta.get_field("posting_date"): + translation_date = self.posting_date + else: + translation_date = self.transaction_date + # end manqala + if self.meta.get_field("currency"): # price list part fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ @@ -133,7 +140,7 @@ class AccountsController(TransactionBase): elif not self.plc_conversion_rate: # manqala 19/09/16: using the transaction date on the PO or SO as the basis for getting the exchange rate self.plc_conversion_rate = get_exchange_rate( - self.transaction_date, self.price_list_currency, self.company_currency) + translation_date, self.price_list_currency, self.company_currency) # currency if not self.currency: @@ -142,9 +149,10 @@ class AccountsController(TransactionBase): elif self.currency == self.company_currency: self.conversion_rate = 1.0 elif not self.conversion_rate: - # cksgb 19/09/2016: added transaction date to arguments for get_exchange_rate - self.conversion_rate = get_exchange_rate(self.transaction_date, self.currency, + # manqala 19/09/2016: added transaction date to arguments for get_exchange_rate + self.conversion_rate = get_exchange_rate(translation_date, self.currency, self.company_currency) + # end manqala def set_missing_item_details(self): """set missing item values""" diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 274e295bbb..34e0de19fe 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -417,15 +417,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, currency: function() { - /* cksgb 19/09/2016: We need to detect if this transaction is a PO/SO. - If so, use transaction_date. - If not, use posting_date as we assume that any other document type is an accounting document. */ - - if (this.frm.doc.doctype == "Purchase Order" || this.frm.doc.doctype == "Sales Order"){ - lookup_date = this.frm.doc.transaction_date; - }else{ - lookup_date = this.frm.doc.posting_date; - } + /* manqala 19/09/2016: let the translation date be whichever of the transaction_date or posting_date is available */ + translation_date = this.frm.doc.transaction_date || this.frm.doc.posting_date; + /* end manqala */ var me = this; this.set_dynamic_labels(); @@ -434,7 +428,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(lookup_date, this.frm.doc.currency, company_currency, + this.get_exchange_rate(translation_date, this.frm.doc.currency, company_currency, function(exchange_rate) { me.frm.set_value("conversion_rate", exchange_rate); }); @@ -462,11 +456,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - get_exchange_rate: function(posting_date, from_currency, to_currency, callback) { + get_exchange_rate: function(translation_date, from_currency, to_currency, callback) { return frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { - posting_date: posting_date, + translation_date: translation_date, from_currency: from_currency, to_currency: to_currency }, diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index e18a9931a2..0733b7c419 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -65,16 +65,16 @@ def before_tests(): frappe.db.commit() @frappe.whitelist() -def get_exchange_rate(posting_date, from_currency, to_currency): - if not (posting_date and from_currency and to_currency): - # cksgb 19/09/2016: Should this be an empty return or should it throw and exception? +def get_exchange_rate(translation_date, from_currency, to_currency): + if not (translation_date and from_currency and to_currency): + # manqala 19/09/2016: Should this be an empty return or should it throw and exception? return if from_currency == to_currency: return 1 # cksgb 19/09/2016: get all entries in Currency Exchange with from_currency and to_currency. Order by date desc. Top one is the required exchange rate - 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") + entries = frappe.get_all("Currency Exchange", fields = ["*"], filters=[["date", "<=", get_datetime_str(translation_date)], ["from_currency", "=", from_currency], ["to_currency", "=", to_currency]], order_by="date desc") if entries: return flt(entries[0].exchange_rate) @@ -96,5 +96,5 @@ def get_exchange_rate(posting_date, from_currency, to_currency): 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)) + frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, translation_date)) return 0.0 \ No newline at end of file From 4a0c8400762adb857c8e929d3af56ba83d8c3f76 Mon Sep 17 00:00:00 2001 From: Chude Osiegbu Date: Tue, 20 Sep 2016 19:34:10 +0100 Subject: [PATCH 08/14] Fixing a regression in shopping_cart_settings that was caused by the change in structure of Currency Exchange Doctype --- .../shopping_cart_settings.py | 98 ++++++++++--------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index d8d00efa96..c206ba276a 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -8,73 +8,81 @@ import frappe from frappe import _, msgprint from frappe.utils import comma_and from frappe.model.document import Document +from frappe.utils import get_datetime, get_datetime_str, now_datetime class ShoppingCartSetupError(frappe.ValidationError): pass class ShoppingCartSettings(Document): - def onload(self): - self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") + def onload(self): + self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") - def validate(self): - if self.enabled: - self.validate_exchange_rates_exist() + def validate(self): + if self.enabled: + self.validate_exchange_rates_exist() - def validate_exchange_rates_exist(self): - """check if exchange rates exist for all Price List currencies (to company's currency)""" - company_currency = frappe.db.get_value("Company", self.company, "default_currency") - if not company_currency: - msgprint(_("Please specify currency in Company") + ": " + self.company, - raise_exception=ShoppingCartSetupError) + def validate_exchange_rates_exist(self): + """check if exchange rates exist for all Price List currencies (to company's currency)""" + company_currency = frappe.db.get_value("Company", self.company, "default_currency") + if not company_currency: + msgprint(_("Please specify currency in Company") + ": " + self.company, + raise_exception=ShoppingCartSetupError) - price_list_currency_map = frappe.db.get_values("Price List", - [self.price_list], - "currency") + price_list_currency_map = frappe.db.get_values("Price List", + [self.price_list], + "currency") - # check if all price lists have a currency - for price_list, currency in price_list_currency_map.items(): - if not currency: - frappe.throw(_("Currency is required for Price List {0}").format(price_list)) + # check if all price lists have a currency + for price_list, currency in price_list_currency_map.items(): + if not currency: + frappe.throw(_("Currency is required for Price List {0}").format(price_list)) - expected_to_exist = [currency + "-" + company_currency - for currency in price_list_currency_map.values() - if currency != company_currency] + expected_to_exist = [currency + "-" + company_currency + for currency in price_list_currency_map.values() + if currency != company_currency] + + # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange + from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency] + to_currency = company_currency + # manqala end - if expected_to_exist: - exists = frappe.db.sql_list("""select name from `tabCurrency Exchange` - where name in (%s)""" % (", ".join(["%s"]*len(expected_to_exist)),), - tuple(expected_to_exist)) + if expected_to_exist: + # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange. + # exchange rates defined with date less than the date on which this document is being saved will be selected + exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange` + where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency)) + # manqala end - missing = list(set(expected_to_exist).difference(exists)) + missing = list(set(expected_to_exist).difference(exists)) - if missing: - msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)), - raise_exception=ShoppingCartSetupError) + if missing: + msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)), + raise_exception=ShoppingCartSetupError) - def validate_tax_rule(self): - if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"): - frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError) + def validate_tax_rule(self): + if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"): + frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError) - def get_tax_master(self, billing_territory): - tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", - "sales_taxes_and_charges_master") - return tax_master and tax_master[0] or None + def get_tax_master(self, billing_territory): + tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", + "sales_taxes_and_charges_master") + return tax_master and tax_master[0] or None - def get_shipping_rules(self, shipping_territory): - return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") + def get_shipping_rules(self, shipping_territory): + return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") def validate_cart_settings(doc, method): - frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate") + frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate") def get_shopping_cart_settings(): - if not getattr(frappe.local, "shopping_cart_settings", None): - frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings") + if not getattr(frappe.local, "shopping_cart_settings", None): + frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings") - return frappe.local.shopping_cart_settings + return frappe.local.shopping_cart_settings def is_cart_enabled(): - return get_shopping_cart_settings().enabled + return get_shopping_cart_settings().enabled def check_shopping_cart_enabled(): - if not get_shopping_cart_settings().enabled: - frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError) + if not get_shopping_cart_settings().enabled: + frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError) From 87d70279c637fd583d0e104b2046b457f4c85b5f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 Dec 2016 14:43:11 +0530 Subject: [PATCH 09/14] get exchange rate on change of date --- erpnext/public/js/controllers/transaction.js | 4 + erpnext/setup/utils.py | 156 ++++++++++--------- 2 files changed, 85 insertions(+), 75 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 18e16c2d0c..b82f142812 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -413,6 +413,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ transaction_date: function() { if (this.frm.doc.transaction_date) { this.frm.transaction_date = this.frm.doc.transaction_date; + frappe.ui.form.trigger(me.frm.doc.doctype, "currency"); } }, @@ -434,9 +435,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r, rt) { if(r.message) { me.frm.set_value("due_date", r.message); + frappe.ui.form.trigger(me.frm.doc.doctype, "currency"); } } }) + } else { + frappe.ui.form.trigger(me.frm.doc.doctype, "currency"); } } }, diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 0733b7c419..3adca5671e 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -6,95 +6,101 @@ 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(translation_date, from_currency, to_currency): - if not (translation_date and from_currency and to_currency): - # manqala 19/09/2016: Should this be an empty return or should it throw and exception? - return - - if from_currency == to_currency: - return 1 - - # cksgb 19/09/2016: get all entries in Currency Exchange with from_currency and to_currency. Order by date desc. Top one is the required exchange rate - entries = frappe.get_all("Currency Exchange", fields = ["*"], filters=[["date", "<=", get_datetime_str(translation_date)], ["from_currency", "=", from_currency], ["to_currency", "=", to_currency]], order_by="date desc") - if entries: - return flt(entries[0].exchange_rate) + if not (translation_date and from_currency and to_currency): + # manqala 19/09/2016: Should this be an empty return or should it throw and exception? + return + + if from_currency == to_currency: + return 1 + + # cksgb 19/09/2016: get all entries in Currency Exchange with from_currency and to_currency. Order by date desc. Top one is the required exchange rate + entries = frappe.get_all("Currency Exchange", fields = ["exchange_rate"], + filters=[ + ["date", "<=", get_datetime_str(translation_date)], + ["from_currency", "=", from_currency], + ["to_currency", "=", to_currency] + ], order_by="date desc", limit=1) + + if entries: + return flt(entries[0].exchange_rate) - 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} for key date {2}").format(from_currency, to_currency, translation_date)) - return 0.0 \ No newline at end of file + return flt(value) + except: + frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, translation_date)) + return 0.0 \ No newline at end of file From 288a18e0cc74a58fdd2f109b8b9f8b04a516c1d1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 Dec 2016 15:36:23 +0530 Subject: [PATCH 10/14] Fixes and patch for Currency Exchange based on date --- erpnext/controllers/accounts_controller.py | 8 +-- erpnext/manufacturing/doctype/bom/bom.py | 4 +- erpnext/patches.txt | 3 +- .../v7_1/set_currency_exchange_date.py | 9 ++++ erpnext/public/js/controllers/transaction.js | 10 ++-- .../currency_exchange/currency_exchange.json | 10 +--- .../test_currency_exchange.py | 17 +++++- .../currency_exchange/test_records.json | 6 +-- erpnext/setup/utils.py | 12 ++--- .../material_request_item.json | 52 ++++++++++++++++++- 10 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 erpnext/patches/v7_1/set_currency_exchange_date.py diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f726db819b..fb3333006e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -128,9 +128,9 @@ class AccountsController(TransactionBase): def set_price_list_currency(self, buying_or_selling): if self.meta.get_field("posting_date"): - translation_date = self.posting_date + transaction_date = self.posting_date else: - translation_date = self.transaction_date + transaction_date = self.transaction_date if self.meta.get_field("currency"): # price list part @@ -144,7 +144,7 @@ class AccountsController(TransactionBase): self.plc_conversion_rate = 1.0 elif not self.plc_conversion_rate: - self.plc_conversion_rate = get_exchange_rate(translation_date, + self.plc_conversion_rate = get_exchange_rate(transaction_date, self.price_list_currency, self.company_currency) # currency @@ -154,7 +154,7 @@ class AccountsController(TransactionBase): elif self.currency == self.company_currency: self.conversion_rate = 1.0 elif not self.conversion_rate: - self.conversion_rate = get_exchange_rate(translation_date, self.currency, + self.conversion_rate = get_exchange_rate(transaction_date, self.currency, self.company_currency) def set_missing_item_details(self, for_validate=False): diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2a27922c7f..783df20d97 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, cstr, flt +from frappe.utils import cint, cstr, flt, nowdate from frappe import _ from erpnext.setup.utils import get_exchange_rate from frappe.model.document import Document @@ -229,7 +229,7 @@ class BOM(Document): frappe.throw(_("Currency of the price list {0} is not similar with the selected currency {1}").format(self.buying_price_list, self.currency)) def set_conversion_rate(self): - self.conversion_rate = get_exchange_rate(self.currency, self.company_currency()) + self.conversion_rate = get_exchange_rate(nowdate(), self.currency, self.company_currency()) def validate_materials(self): """ Validate raw material entries """ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c4b66e7a12..94212ec62e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -354,4 +354,5 @@ erpnext.patches.v7_1.rename_quality_inspection_field erpnext.patches.v7_0.update_autoname_field erpnext.patches.v7_1.update_bom_base_currency erpnext.patches.v7_0.update_status_of_po_so -erpnext.patches.v7_1.set_budget_against_as_cost_center \ No newline at end of file +erpnext.patches.v7_1.set_budget_against_as_cost_center +erpnext.patches.v7_1.set_currency_exchange_date \ No newline at end of file diff --git a/erpnext/patches/v7_1/set_currency_exchange_date.py b/erpnext/patches/v7_1/set_currency_exchange_date.py new file mode 100644 index 0000000000..7d8e4f0415 --- /dev/null +++ b/erpnext/patches/v7_1/set_currency_exchange_date.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + frappe.reload_doctype("Currency Exchange") + frappe.db.sql(""" + update `tabCurrency Exchange` + set `date` = '2010-01-01' + where date is null or date = '' or date = '0000-00-00' + """) \ No newline at end of file diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b82f142812..c31b0c84a8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -413,7 +413,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ transaction_date: function() { if (this.frm.doc.transaction_date) { this.frm.transaction_date = this.frm.doc.transaction_date; - frappe.ui.form.trigger(me.frm.doc.doctype, "currency"); + frappe.ui.form.trigger(this.frm.doc.doctype, "currency"); } }, @@ -455,7 +455,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ currency: function() { /* manqala 19/09/2016: let the translation date be whichever of the transaction_date or posting_date is available */ - translation_date = this.frm.doc.transaction_date || this.frm.doc.posting_date; + var transaction_date = this.frm.doc.transaction_date || this.frm.doc.posting_date; /* end manqala */ var me = this; @@ -465,7 +465,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(translation_date, this.frm.doc.currency, company_currency, + this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency, function(exchange_rate) { me.frm.set_value("conversion_rate", exchange_rate); }); @@ -493,11 +493,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - get_exchange_rate: function(translation_date, from_currency, to_currency, callback) { + get_exchange_rate: function(transaction_date, from_currency, to_currency, callback) { return frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { - translation_date: translation_date, + transaction_date: transaction_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 56e914a691..76e1a6b97e 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.json +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.json @@ -79,12 +79,8 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, -<<<<<<< HEAD "in_list_view": 1, "in_standard_filter": 1, -======= - "in_list_view": 0, ->>>>>>> 4a0c8400762adb857c8e929d3af56ba83d8c3f76 "label": "To Currency", "length": 0, "no_copy": 0, @@ -142,11 +138,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, -<<<<<<< HEAD - "modified": "2016-11-07 05:28:09.772560", -======= - "modified": "2016-09-05 22:47:38.746711", ->>>>>>> 4a0c8400762adb857c8e929d3af56ba83d8c3f76 + "modified": "2016-11-08 05:28:09.772560", "modified_by": "Administrator", "module": "Setup", "name": "Currency Exchange", diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 00776307de..69ca1c2a10 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -3,5 +3,18 @@ from __future__ import unicode_literals -import frappe -test_records = frappe.get_test_records('Currency Exchange') \ No newline at end of file +import frappe, unittest +test_records = frappe.get_test_records('Currency Exchange') + +class TestCurrencyExchange(unittest.TestCase): + def test_exchnage_rate(self): + from erpnext.setup.utils import get_exchange_rate + + # Exchange rate as on 15th Jan, 2016, should be fetched from Currency Exchange record + exchange_rate = get_exchange_rate("2016-01-15", "USD", "INR") + self.assertEqual(exchange_rate, 60.0) + + # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io + exchange_rate = get_exchange_rate("2015-12-15", "USD", "INR") + self.assertFalse(exchange_rate==60) + \ No newline at end of file diff --git a/erpnext/setup/doctype/currency_exchange/test_records.json b/erpnext/setup/doctype/currency_exchange/test_records.json index 17ca1a5f24..23edd8a799 100644 --- a/erpnext/setup/doctype/currency_exchange/test_records.json +++ b/erpnext/setup/doctype/currency_exchange/test_records.json @@ -1,21 +1,21 @@ [ { "doctype": "Currency Exchange", - "date": "01-01-2016", + "date": "2016-01-01", "exchange_rate": 60.0, "from_currency": "USD", "to_currency": "INR" }, { "doctype": "Currency Exchange", - "date": "01-01-2016", + "date": "2016-01-01", "exchange_rate": 0.773, "from_currency": "USD", "to_currency": "EUR" }, { "doctype": "Currency Exchange", - "date": "01-01-2016", + "date": "2016-01-01", "exchange_rate": 0.0167, "from_currency": "INR", "to_currency": "USD" diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 3adca5671e..9a8ae07113 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -65,22 +65,22 @@ def before_tests(): frappe.db.commit() @frappe.whitelist() -def get_exchange_rate(translation_date, from_currency, to_currency): - if not (translation_date and from_currency and to_currency): +def get_exchange_rate(transaction_date, from_currency, to_currency): + if not (transaction_date and from_currency and to_currency): # manqala 19/09/2016: Should this be an empty return or should it throw and exception? return if from_currency == to_currency: return 1 - # cksgb 19/09/2016: get all entries in Currency Exchange with from_currency and to_currency. Order by date desc. Top one is the required exchange rate + # cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency. entries = frappe.get_all("Currency Exchange", fields = ["exchange_rate"], filters=[ - ["date", "<=", get_datetime_str(translation_date)], + ["date", "<=", get_datetime_str(transaction_date)], ["from_currency", "=", from_currency], ["to_currency", "=", to_currency] ], order_by="date desc", limit=1) - + if entries: return flt(entries[0].exchange_rate) @@ -102,5 +102,5 @@ def get_exchange_rate(translation_date, from_currency, to_currency): return flt(value) except: - frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, translation_date)) + frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, transaction_date)) return 0.0 \ No newline at end of file diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index f7ca7d9915..b1d6ec0cb6 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -23,6 +23,7 @@ "ignore_xss_filter": 0, "in_filter": 1, "in_list_view": 1, + "in_standard_filter": 0, "label": "Item Code", "length": 0, "no_copy": 0, @@ -34,6 +35,7 @@ "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 1, @@ -53,12 +55,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -77,6 +81,7 @@ "ignore_xss_filter": 0, "in_filter": 1, "in_list_view": 0, + "in_standard_filter": 0, "label": "Item Name", "length": 0, "no_copy": 0, @@ -87,6 +92,7 @@ "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 1, @@ -106,6 +112,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Description", "length": 0, "no_copy": 0, @@ -114,6 +121,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -132,6 +140,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Description", "length": 0, "no_copy": 0, @@ -142,6 +151,7 @@ "print_hide_if_no_value": 0, "print_width": "250px", "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -161,6 +171,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -168,6 +179,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -186,6 +198,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Image", "length": 0, "no_copy": 0, @@ -194,6 +207,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -212,6 +226,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Quantity and Warehouse", "length": 0, "no_copy": 0, @@ -219,6 +234,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -237,6 +253,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Quantity", "length": 0, "no_copy": 0, @@ -247,6 +264,7 @@ "print_hide_if_no_value": 0, "print_width": "80px", "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -266,6 +284,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Stock UOM", "length": 0, "no_copy": 0, @@ -277,6 +296,7 @@ "print_hide_if_no_value": 0, "print_width": "70px", "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -296,6 +316,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "For Warehouse", "length": 0, "no_copy": 0, @@ -307,6 +328,7 @@ "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -326,12 +348,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -351,6 +375,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Required Date", "length": 0, "no_copy": 0, @@ -361,6 +386,7 @@ "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -380,6 +406,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "More Information", "length": 0, "no_copy": 0, @@ -387,6 +414,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -406,6 +434,7 @@ "ignore_xss_filter": 0, "in_filter": 1, "in_list_view": 0, + "in_standard_filter": 0, "label": "Item Group", "length": 0, "no_copy": 0, @@ -416,6 +445,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 1, @@ -434,6 +464,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Brand", "length": 0, "no_copy": 0, @@ -445,6 +476,7 @@ "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -464,6 +496,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Lead Time Date", "length": 0, "no_copy": 1, @@ -473,6 +506,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -491,6 +525,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Sales Order", "length": 0, "no_copy": 0, @@ -499,6 +534,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -517,6 +553,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Project", "length": 0, "no_copy": 0, @@ -526,6 +563,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -544,12 +582,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -568,6 +608,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Min Order Qty", "length": 0, "no_copy": 1, @@ -578,6 +619,7 @@ "print_hide_if_no_value": 0, "print_width": "70px", "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -597,6 +639,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Projected Qty", "length": 0, "no_copy": 1, @@ -607,6 +650,7 @@ "print_hide_if_no_value": 0, "print_width": "70px", "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -626,6 +670,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Actual Qty", "length": 0, "no_copy": 1, @@ -634,6 +679,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 1, "reqd": 0, "search_index": 0, @@ -652,6 +698,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Completed Qty", "length": 0, "no_copy": 1, @@ -661,6 +708,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -679,6 +727,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Page Break", "length": 0, "no_copy": 1, @@ -688,6 +737,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -705,7 +755,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-10-17 04:58:33.317145", + "modified": "2016-12-08 14:49:48.397015", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", From 3113f00c463e59a0b85902f155c1c18567ed9ac6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 Dec 2016 15:42:44 +0530 Subject: [PATCH 11/14] Replaced spaces with tabs --- erpnext/controllers/accounts_controller.py | 172 ++++++++++----------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fb3333006e..b998bbd9ba 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -609,125 +609,125 @@ class AccountsController(TransactionBase): @frappe.whitelist() def get_tax_rate(account_head): - return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) + return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) @frappe.whitelist() def get_default_taxes_and_charges(master_doctype): - default_tax = frappe.db.get_value(master_doctype, {"is_default": 1}) - return get_taxes_and_charges(master_doctype, default_tax) + default_tax = frappe.db.get_value(master_doctype, {"is_default": 1}) + return get_taxes_and_charges(master_doctype, default_tax) @frappe.whitelist() def get_taxes_and_charges(master_doctype, master_name): - if not master_name: - return - from frappe.model import default_fields - tax_master = frappe.get_doc(master_doctype, master_name) + if not master_name: + return + from frappe.model import default_fields + tax_master = frappe.get_doc(master_doctype, master_name) - taxes_and_charges = [] - for i, tax in enumerate(tax_master.get("taxes")): - tax = tax.as_dict() + taxes_and_charges = [] + for i, tax in enumerate(tax_master.get("taxes")): + tax = tax.as_dict() - for fieldname in default_fields: - if fieldname in tax: - del tax[fieldname] + for fieldname in default_fields: + if fieldname in tax: + del tax[fieldname] - taxes_and_charges.append(tax) + taxes_and_charges.append(tax) - return taxes_and_charges + return taxes_and_charges def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company): - """common validation for currency and price list currency""" + """common validation for currency and price list currency""" - company_currency = frappe.db.get_value("Company", company, "default_currency", cache=True) + company_currency = frappe.db.get_value("Company", company, "default_currency", cache=True) - if not conversion_rate: - throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format( - conversion_rate_label, currency, company_currency)) + if not conversion_rate: + throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format( + conversion_rate_label, currency, company_currency)) def validate_taxes_and_charges(tax): - if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id: - frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'")) - elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']: - if cint(tax.idx) == 1: - frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) - elif not tax.row_id: - frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}".format(tax.idx, _(tax.doctype)))) - elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): - frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) + if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id: + frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'")) + elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']: + if cint(tax.idx) == 1: + frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) + elif not tax.row_id: + frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}".format(tax.idx, _(tax.doctype)))) + elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): + frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) - if tax.charge_type == "Actual": - tax.rate = None + if tax.charge_type == "Actual": + tax.rate = None def validate_inclusive_tax(tax, doc): - def _on_previous_row_error(row_range): - throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, - row_range)) + def _on_previous_row_error(row_range): + throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, + row_range)) - if cint(getattr(tax, "included_in_print_rate", None)): - if tax.charge_type == "Actual": - # inclusive tax cannot be of type Actual - throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx)) - elif tax.charge_type == "On Previous Row Amount" and \ - not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate): - # referred row should also be inclusive - _on_previous_row_error(tax.row_id) - elif tax.charge_type == "On Previous Row Total" and \ - not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]): - # all rows about the reffered tax should be inclusive - _on_previous_row_error("1 - %d" % (tax.row_id,)) - elif tax.get("category") == "Valuation": - frappe.throw(_("Valuation type charges can not marked as Inclusive")) + if cint(getattr(tax, "included_in_print_rate", None)): + if tax.charge_type == "Actual": + # inclusive tax cannot be of type Actual + throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx)) + elif tax.charge_type == "On Previous Row Amount" and \ + not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate): + # referred row should also be inclusive + _on_previous_row_error(tax.row_id) + elif tax.charge_type == "On Previous Row Total" and \ + not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]): + # all rows about the reffered tax should be inclusive + _on_previous_row_error("1 - %d" % (tax.row_id,)) + elif tax.get("category") == "Valuation": + frappe.throw(_("Valuation type charges can not marked as Inclusive")) def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): - if (not conversion_rate) and (account_currency!=company_currency): - frappe.throw(_("Account: {0} with currency: {1} can not be selected") - .format(gl_dict.account, account_currency)) + if (not conversion_rate) and (account_currency!=company_currency): + frappe.throw(_("Account: {0} with currency: {1} can not be selected") + .format(gl_dict.account, account_currency)) - gl_dict["account_currency"] = company_currency if account_currency==company_currency \ - else account_currency + gl_dict["account_currency"] = company_currency if account_currency==company_currency \ + else account_currency - # set debit/credit in account currency if not provided - if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency): - gl_dict.debit_in_account_currency = gl_dict.debit if account_currency==company_currency \ - else flt(gl_dict.debit / conversion_rate, 2) + # set debit/credit in account currency if not provided + if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency): + gl_dict.debit_in_account_currency = gl_dict.debit if account_currency==company_currency \ + else flt(gl_dict.debit / conversion_rate, 2) - if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency): - gl_dict.credit_in_account_currency = gl_dict.credit if account_currency==company_currency \ - else flt(gl_dict.credit / conversion_rate, 2) + if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency): + gl_dict.credit_in_account_currency = gl_dict.credit if account_currency==company_currency \ + else flt(gl_dict.credit / conversion_rate, 2) def get_advance_journal_entries(party_type, party, party_account, amount_field, - order_doctype, order_list, include_unallocated=True): + order_doctype, order_list, include_unallocated=True): - dr_or_cr = "credit_in_account_currency" if party_type=="Customer" else "debit_in_account_currency" + dr_or_cr = "credit_in_account_currency" if party_type=="Customer" else "debit_in_account_currency" - conditions = [] - if include_unallocated: - conditions.append("ifnull(t2.reference_name, '')=''") + conditions = [] + if include_unallocated: + conditions.append("ifnull(t2.reference_name, '')=''") - if order_list: - order_condition = ', '.join(['%s'] * len(order_list)) - conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))"\ - .format(order_doctype, order_condition)) + if order_list: + order_condition = ', '.join(['%s'] * len(order_list)) + conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))"\ + .format(order_doctype, order_condition)) - reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else "" - - journal_entries = frappe.db.sql(""" - select - "Journal Entry" as reference_type, t1.name as reference_name, - t1.remark as remarks, t2.{0} as amount, t2.name as reference_row, - t2.reference_name as against_order - from - `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where - t1.name = t2.parent and t2.account = %s - and t2.party_type = %s and t2.party = %s - and t2.is_advance = 'Yes' and t1.docstatus = 1 - and {1} > 0 {2} - order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition), - [party_account, party_type, party] + order_list, as_dict=1) + reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else "" + + journal_entries = frappe.db.sql(""" + select + "Journal Entry" as reference_type, t1.name as reference_name, + t1.remark as remarks, t2.{0} as amount, t2.name as reference_row, + t2.reference_name as against_order + from + `tabJournal Entry` t1, `tabJournal Entry Account` t2 + where + t1.name = t2.parent and t2.account = %s + and t2.party_type = %s and t2.party = %s + and t2.is_advance = 'Yes' and t1.docstatus = 1 + and {1} > 0 {2} + order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition), + [party_account, party_type, party] + order_list, as_dict=1) - return list(journal_entries) + return list(journal_entries) def get_advance_payment_entries(party_type, party, party_account, order_doctype, order_list=None, include_unallocated=True, against_all_orders=False): From aa0d174d3dee1f296721fb3114bd91549d5ae404 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 Dec 2016 15:44:16 +0530 Subject: [PATCH 12/14] Replaced spaces with tabs --- .../shopping_cart_settings.py | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index c206ba276a..6915ef54a1 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -13,76 +13,76 @@ from frappe.utils import get_datetime, get_datetime_str, now_datetime class ShoppingCartSetupError(frappe.ValidationError): pass class ShoppingCartSettings(Document): - def onload(self): - self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") + def onload(self): + self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") - def validate(self): - if self.enabled: - self.validate_exchange_rates_exist() + def validate(self): + if self.enabled: + self.validate_exchange_rates_exist() - def validate_exchange_rates_exist(self): - """check if exchange rates exist for all Price List currencies (to company's currency)""" - company_currency = frappe.db.get_value("Company", self.company, "default_currency") - if not company_currency: - msgprint(_("Please specify currency in Company") + ": " + self.company, - raise_exception=ShoppingCartSetupError) + def validate_exchange_rates_exist(self): + """check if exchange rates exist for all Price List currencies (to company's currency)""" + company_currency = frappe.db.get_value("Company", self.company, "default_currency") + if not company_currency: + msgprint(_("Please specify currency in Company") + ": " + self.company, + raise_exception=ShoppingCartSetupError) - price_list_currency_map = frappe.db.get_values("Price List", - [self.price_list], - "currency") + price_list_currency_map = frappe.db.get_values("Price List", + [self.price_list], + "currency") - # check if all price lists have a currency - for price_list, currency in price_list_currency_map.items(): - if not currency: - frappe.throw(_("Currency is required for Price List {0}").format(price_list)) + # check if all price lists have a currency + for price_list, currency in price_list_currency_map.items(): + if not currency: + frappe.throw(_("Currency is required for Price List {0}").format(price_list)) - expected_to_exist = [currency + "-" + company_currency - for currency in price_list_currency_map.values() - if currency != company_currency] - - # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange - from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency] - to_currency = company_currency - # manqala end + expected_to_exist = [currency + "-" + company_currency + for currency in price_list_currency_map.values() + if currency != company_currency] + + # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange + from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency] + to_currency = company_currency + # manqala end - if expected_to_exist: - # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange. - # exchange rates defined with date less than the date on which this document is being saved will be selected - exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange` - where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency)) - # manqala end + if expected_to_exist: + # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange. + # exchange rates defined with date less than the date on which this document is being saved will be selected + exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange` + where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency)) + # manqala end - missing = list(set(expected_to_exist).difference(exists)) + missing = list(set(expected_to_exist).difference(exists)) - if missing: - msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)), - raise_exception=ShoppingCartSetupError) + if missing: + msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)), + raise_exception=ShoppingCartSetupError) - def validate_tax_rule(self): - if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"): - frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError) + def validate_tax_rule(self): + if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"): + frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError) - def get_tax_master(self, billing_territory): - tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", - "sales_taxes_and_charges_master") - return tax_master and tax_master[0] or None + def get_tax_master(self, billing_territory): + tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", + "sales_taxes_and_charges_master") + return tax_master and tax_master[0] or None - def get_shipping_rules(self, shipping_territory): - return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") + def get_shipping_rules(self, shipping_territory): + return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") def validate_cart_settings(doc, method): - frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate") + frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate") def get_shopping_cart_settings(): - if not getattr(frappe.local, "shopping_cart_settings", None): - frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings") + if not getattr(frappe.local, "shopping_cart_settings", None): + frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings") - return frappe.local.shopping_cart_settings + return frappe.local.shopping_cart_settings def is_cart_enabled(): - return get_shopping_cart_settings().enabled + return get_shopping_cart_settings().enabled def check_shopping_cart_enabled(): - if not get_shopping_cart_settings().enabled: - frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError) + if not get_shopping_cart_settings().enabled: + frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError) From 352d95344e0e6be3e30d77a0d3591d9dc42d1095 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 Dec 2016 16:06:47 +0530 Subject: [PATCH 13/14] Set exchange rate on change of date for Journal/Payment Entry --- .../accounts/doctype/journal_entry/journal_entry.js | 10 +++++++++- .../accounts/doctype/payment_entry/payment_entry.js | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index ba650cd462..c98e77fdd2 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -35,6 +35,14 @@ frappe.ui.form.on("Journal Entry", { multi_currency: function(frm) { erpnext.journal_entry.toggle_fields_based_on_currency(frm); + }, + + posting_date: function(frm) { + if(!frm.doc.multi_currency) return; + + $.each(frm.doc.accounts || [], function(i, row) { + erpnext.journal_entry.set_exchange_rate(frm, row.doctype, row.name); + }) } }) @@ -345,7 +353,7 @@ frappe.ui.form.on("Journal Entry Account", { }); } }, - + debit_in_account_currency: function(frm, cdt, cdn) { erpnext.journal_entry.set_exchange_rate(frm, cdt, cdn); }, diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index f59d3e70fd..d3dbd314cc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -327,7 +327,7 @@ frappe.ui.form.on('Payment Entry', { frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { - posting_date: frm.doc.posting_date, + transaction_date: frm.doc.posting_date, from_currency: from_currency, to_currency: to_currency }, @@ -336,6 +336,10 @@ frappe.ui.form.on('Payment Entry', { } }) }, + + posting_date: function(frm) { + frm.events.paid_from_account_currency(frm); + }, source_exchange_rate: function(frm) { if (frm.doc.paid_amount) { From ad4f1c7fd14d713ef321678f5674284dd39e5089 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 9 Dec 2016 12:14:47 +0530 Subject: [PATCH 14/14] date is optional in get_exchange_rate --- .../doctype/journal_entry/journal_entry.py | 20 +++++++++------- .../doctype/payment_entry/payment_entry.py | 24 +++++++++++-------- .../payment_request/test_payment_request.py | 4 +--- erpnext/accounts/doctype/sales_invoice/pos.py | 5 ++-- .../quoted_item_comparison.py | 3 +-- erpnext/controllers/accounts_controller.py | 8 +++---- .../crm/doctype/opportunity/opportunity.py | 3 ++- erpnext/demo/user/purchase.py | 3 +-- erpnext/demo/user/sales.py | 3 +-- erpnext/manufacturing/doctype/bom/bom.py | 4 ++-- .../test_currency_exchange.py | 4 ++-- erpnext/setup/utils.py | 8 ++++--- erpnext/stock/get_item_details.py | 3 ++- 13 files changed, 49 insertions(+), 43 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index c89e24dc21..cd79363502 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -327,7 +327,7 @@ class JournalEntry(AccountsController): d.exchange_rate = 1 elif not d.exchange_rate or d.exchange_rate == 1 or \ (d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name and d.posting_date): - # Modified to include the posting date for which to retreive the exchange rate + # Modified to include the posting date for which to retreive the exchange rate d.exchange_rate = get_exchange_rate(self.posting_date, d.account, d.account_currency, self.company, d.reference_type, d.reference_name, d.debit, d.credit, d.exchange_rate) @@ -649,7 +649,8 @@ def get_payment_entry(ref_doc, args): cost_center = frappe.db.get_value("Company", ref_doc.company, "cost_center") exchange_rate = 1 if args.get("party_account"): - # Modified to include the posting date for which the exchange rate is required. Assumed to be the posting date in the reference document + # Modified to include the posting date for which the exchange rate is required. + # Assumed to be the posting date in the reference document exchange_rate = get_exchange_rate(ref_doc.posting_date, args.get("party_account"), args.get("party_account_currency"), ref_doc.company, ref_doc.doctype, ref_doc.name) @@ -683,8 +684,8 @@ def get_payment_entry(ref_doc, args): bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account")) if bank_account: bank_row.update(bank_account) - # Modified to include the posting date for which the exchange rate is required. Assumed to be the posting date of the - # reference date + # Modified to include the posting date for which the exchange rate is required. + # Assumed to be the posting date of the reference date bank_row.exchange_rate = get_exchange_rate(ref_doc.posting_date, bank_account["account"], bank_account["account_currency"], ref_doc.company) @@ -809,8 +810,9 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi "party_type": party_type, "account_type": account_details.account_type, "account_currency": account_details.account_currency or company_currency, - # The date used to retreive the exchange rate here is the date passed in as an argument to this function. - # It is assumed to be the date on which the balance is sought + + # The date used to retreive the exchange rate here is the date passed in + # as an argument to this function. It is assumed to be the date on which the balance is sought "exchange_rate": get_exchange_rate(date, account, account_details.account_currency, company, debit=debit, credit=credit, exchange_rate=exchange_rate) } @@ -849,10 +851,10 @@ def get_exchange_rate(posting_date, account, account_currency=None, company=None (account_details.root_type == "Liability" and debit)): exchange_rate = get_average_exchange_rate(account) + # The date used to retreive the exchange rate here is the date passed + # in as an argument to this function. if not exchange_rate and account_currency and posting_date: - # The date used to retreive the exchange rate here is the date passed in as an argument to this function. - exchange_rate = get_exchange_rate(posting_date, account_currency, company_currency) - + exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) else: exchange_rate = 1 diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ef6eec97a8..3e10b51549 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -152,12 +152,12 @@ class PaymentEntry(AccountsController): elif self.payment_type in ("Pay", "Internal Transfer"): self.source_exchange_rate = get_average_exchange_rate(self.paid_from) else: - self.source_exchange_rate = get_exchange_rate(self.posting_date, self.paid_from_account_currency, - self.company_currency) + self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency, + self.company_currency, self.posting_date) if self.paid_to and not self.target_exchange_rate: - self.target_exchange_rate = get_exchange_rate(self.posting_date, self.paid_to_account_currency, - self.company_currency) + self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency, + self.company_currency, self.posting_date) def validate_mandatory(self): for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"): @@ -517,8 +517,9 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre order_list = [] for d in orders: d["voucher_type"] = voucher_type - # This assumes that the exchange rate required is the one in the SO - d["exchange_rate"] = get_exchange_rate(posting_date,party_account_currency, company_currency) + # This assumes that the exchange rate required is the one in the SO + d["exchange_rate"] = get_exchange_rate(party_account_currency, + company_currency, posting_date) order_list.append(d) return order_list @@ -593,16 +594,19 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre exchange_rate = 1 else: total_amount = ref_doc.grand_total - # Get the exchange rate from the original ref doc or get it based on the posting date of the ref doc + + # Get the exchange rate from the original ref doc + # or get it based on the posting date of the ref doc exchange_rate = ref_doc.get("conversion_rate") or \ - get_exchange_rate(ref_doc.posting_date, party_account_currency, ref_doc.company_currency) + get_exchange_rate(party_account_currency, ref_doc.company_currency, ref_doc.posting_date) outstanding_amount = ref_doc.get("outstanding_amount") \ if reference_doctype in ("Sales Invoice", "Purchase Invoice") \ else flt(total_amount) - flt(ref_doc.advance_paid) else: - # Get the exchange rate based on the posting date of the ref doc - exchange_rate = get_exchange_rate(ref_doc.posting_date, party_account_currency, ref_doc.company_currency) + # Get the exchange rate based on the posting date of the ref doc + exchange_rate = get_exchange_rate(party_account_currency, + ref_doc.company_currency, ref_doc.posting_date) return frappe._dict({ "due_date": ref_doc.get("due_date"), diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index d695bf6cc8..bf3e24fe7a 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -9,8 +9,6 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.setup.utils import get_exchange_rate -from frappe.utils import nowdate -# test_records = frappe.get_test_records('Payment Request') test_dependencies = ["Currency Exchange", "Journal Entry", "Contact", "Address"] @@ -53,7 +51,7 @@ class TestPaymentRequest(unittest.TestCase): self.assertEquals(pr.reference_name, so_inr.name) self.assertEquals(pr.currency, "INR") - conversion_rate = get_exchange_rate(nowdate(), "USD", "INR") + conversion_rate = get_exchange_rate("USD", "INR") si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate) pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com") diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index eeb556082e..9fb11f2b7c 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import frappe, json -from frappe import _ from frappe.utils import nowdate from erpnext.setup.utils import get_exchange_rate from erpnext.stock.get_item_details import get_pos_profile @@ -63,8 +62,10 @@ def update_pos_profile_data(doc, pos_profile, company_data): doc.currency = pos_profile.get('currency') or company_data.default_currency doc.conversion_rate = 1.0 + if doc.currency != company_data.default_currency: - doc.conversion_rate = get_exchange_rate(doc.posting_date, doc.currency, company_data.default_currency) + doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency, doc.posting_date) + doc.selling_price_list = pos_profile.get('selling_price_list') or \ frappe.db.get_value('Selling Settings', None, 'selling_price_list') doc.naming_series = pos_profile.get('naming_series') or 'SINV-' diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index 2b024ef385..03f7a34079 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from erpnext.setup.utils import get_exchange_rate -from erpnext.utils import nowdate import frappe @@ -40,7 +39,7 @@ def get_quote_list(item, qty_list): #Add a row for each supplier for root in set(suppliers): supplier_currency = frappe.db.get_value("Supplier",root,"default_currency") - exg = get_exchange_rate(nowdate(),supplier_currency,company_currency) + exg = get_exchange_rate(supplier_currency, company_currency) row = frappe._dict({ "supplier_name": root diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b998bbd9ba..554529c6f6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -144,8 +144,8 @@ class AccountsController(TransactionBase): self.plc_conversion_rate = 1.0 elif not self.plc_conversion_rate: - self.plc_conversion_rate = get_exchange_rate(transaction_date, - self.price_list_currency, self.company_currency) + self.plc_conversion_rate = get_exchange_rate(self.price_list_currency, + self.company_currency, transaction_date) # currency if not self.currency: @@ -154,8 +154,8 @@ class AccountsController(TransactionBase): elif self.currency == self.company_currency: self.conversion_rate = 1.0 elif not self.conversion_rate: - self.conversion_rate = get_exchange_rate(transaction_date, self.currency, - self.company_currency) + self.conversion_rate = get_exchange_rate(self.currency, + self.company_currency, transaction_date) def set_missing_item_details(self, for_validate=False): """set missing item values""" diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 38164cca5a..6ee9003608 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -205,7 +205,8 @@ def make_quotation(source_name, target_doc=None): if company_currency == quotation.currency: exchange_rate = 1 else: - exchange_rate = get_exchange_rate(quotation.transaction_date, quotation.currency, company_currency) + exchange_rate = get_exchange_rate(quotation.currency, company_currency, + quotation.transaction_date) quotation.conversion_rate = exchange_rate diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py index c7ec62b6f4..ab8ec77257 100644 --- a/erpnext/demo/user/purchase.py +++ b/erpnext/demo/user/purchase.py @@ -12,7 +12,6 @@ from erpnext.exceptions import InvalidCurrency from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \ make_supplier_quotation as make_quotation_from_rfq -from frappe.utils.nowdate def work(): frappe.set_user(frappe.db.get_global('demo_purchase_user')) @@ -57,7 +56,7 @@ def work(): if company_currency == party_account_currency: exchange_rate = 1 else: - exchange_rate = get_exchange_rate(nowdate(), party_account_currency, company_currency) + exchange_rate = get_exchange_rate(party_account_currency, company_currency) # make supplier quotations if random.random() < 0.2: diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py index b9e6535e43..10df14334f 100644 --- a/erpnext/demo/user/sales.py +++ b/erpnext/demo/user/sales.py @@ -9,7 +9,6 @@ from frappe.utils.make_random import add_random_children, get_random from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.party import get_party_account_currency from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request, make_payment_entry -from frappe.utils import nowdate def work(): frappe.set_user(frappe.db.get_global('demo_sales_user_2')) @@ -89,7 +88,7 @@ def make_quotation(): if company_currency == party_account_currency: exchange_rate = 1 else: - exchange_rate = get_exchange_rate(nowdate(), party_account_currency, company_currency) + exchange_rate = get_exchange_rate(party_account_currency, company_currency) qtn = frappe.get_doc({ "creation": frappe.flags.current_date, diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 783df20d97..2a27922c7f 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, cstr, flt, nowdate +from frappe.utils import cint, cstr, flt from frappe import _ from erpnext.setup.utils import get_exchange_rate from frappe.model.document import Document @@ -229,7 +229,7 @@ class BOM(Document): frappe.throw(_("Currency of the price list {0} is not similar with the selected currency {1}").format(self.buying_price_list, self.currency)) def set_conversion_rate(self): - self.conversion_rate = get_exchange_rate(nowdate(), self.currency, self.company_currency()) + self.conversion_rate = get_exchange_rate(self.currency, self.company_currency()) def validate_materials(self): """ Validate raw material entries """ diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 69ca1c2a10..181f07257b 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -11,10 +11,10 @@ class TestCurrencyExchange(unittest.TestCase): from erpnext.setup.utils import get_exchange_rate # Exchange rate as on 15th Jan, 2016, should be fetched from Currency Exchange record - exchange_rate = get_exchange_rate("2016-01-15", "USD", "INR") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15") self.assertEqual(exchange_rate, 60.0) # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io - exchange_rate = get_exchange_rate("2015-12-15", "USD", "INR") + exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15") self.assertFalse(exchange_rate==60) \ No newline at end of file diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 9a8ae07113..0c214e4c2a 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -5,7 +5,7 @@ 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 +from frappe.utils import get_datetime_str, nowdate def get_company_currency(company): currency = frappe.db.get_value("Company", company, "default_currency", cache=True) @@ -65,8 +65,10 @@ def before_tests(): frappe.db.commit() @frappe.whitelist() -def get_exchange_rate(transaction_date, from_currency, to_currency): - if not (transaction_date and from_currency and to_currency): +def get_exchange_rate(from_currency, to_currency, transaction_date=None): + if not transaction_date: + transaction_date = nowdate() + if not (from_currency and to_currency): # manqala 19/09/2016: Should this be an empty return or should it throw and exception? return diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c0fe3a6231..16ea58e47a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -482,7 +482,8 @@ def get_price_list_currency_and_exchange_rate(args): if (not plc_conversion_rate) or (price_list_currency and args.price_list_currency \ and price_list_currency != args.price_list_currency): # cksgb 19/09/2016: added args.transaction_date as posting_date argument for get_exchange_rate - plc_conversion_rate = get_exchange_rate(args.transaction_date, price_list_currency, args.currency) or plc_conversion_rate + plc_conversion_rate = get_exchange_rate(price_list_currency, args.currency, + args.transaction_date) or plc_conversion_rate return frappe._dict({ "price_list_currency": price_list_currency,