diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 06ba5dfe086..ba650cd462f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -420,6 +420,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 2f3b101f33a..c89e24dc214 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -326,8 +326,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: @@ -648,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"): - 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") @@ -681,7 +683,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 @@ -805,7 +809,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) } @@ -815,8 +821,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, @@ -842,8 +849,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 1f6199e0523..f59d3e70fd0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -327,6 +327,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 }, @@ -425,6 +426,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 d4f8edbb6f7..ef6eec97a89 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -152,11 +152,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): @@ -482,12 +482,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" @@ -517,7 +517,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 @@ -592,14 +593,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 73c412f2473..d695bf6cc8c 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 4e4ad780ed6..eeb556082e9 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -64,7 +64,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 1793fc31405..2b024ef3855 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/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5796a4da102..f726db819be 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -127,6 +127,11 @@ 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): + if self.meta.get_field("posting_date"): + translation_date = self.posting_date + else: + translation_date = self.transaction_date + if self.meta.get_field("currency"): # price list part fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ @@ -139,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( + self.plc_conversion_rate = get_exchange_rate(translation_date, self.price_list_currency, self.company_currency) # currency @@ -149,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(self.currency, + self.conversion_rate = get_exchange_rate(translation_date, self.currency, self.company_currency) def set_missing_item_details(self, for_validate=False): @@ -602,128 +607,127 @@ class AccountsController(TransactionBase): for item in duplicate_list: self.remove(item) - @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): diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 6be0768bf41..38164cca5ae 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -205,7 +205,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 ab8ec772574..c7ec62b6f4f 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 10df14334fc..b9e6535e43f 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 954b03b1f68..18e16c2d0cf 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -450,6 +450,10 @@ 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; + /* end manqala */ + var me = this; this.set_dynamic_labels(); @@ -457,7 +461,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(translation_date, this.frm.doc.currency, company_currency, function(exchange_rate) { me.frm.set_value("conversion_rate", exchange_rate); }); @@ -485,10 +489,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - get_exchange_rate: function(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: { + translation_date: translation_date, from_currency: from_currency, to_currency: to_currency }, @@ -505,7 +510,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); }); diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.json b/erpnext/setup/doctype/currency_exchange/currency_exchange.json index 44cea2076dd..56e914a691b 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.json +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.json @@ -12,6 +12,33 @@ "editable_grid": 0, "engine": "InnoDB", "fields": [ + { + "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, @@ -38,7 +65,8 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "unique": 0, + "width": "3" }, { "allow_on_submit": 0, @@ -51,8 +79,12 @@ "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, @@ -66,7 +98,8 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "unique": 0, + "width": "3" }, { "allow_on_submit": 0, @@ -94,7 +127,8 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "unique": 0, + "width": "3" } ], "hide_heading": 0, @@ -108,7 +142,11 @@ "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_by": "Administrator", "module": "Setup", "name": "Currency Exchange", @@ -202,5 +240,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 60228129751..7f1a43c0eef 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 784bf262c03..17ca1a5f24a 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 eda2042fd5e..0733b7c419c 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -5,95 +5,96 @@ 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(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 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, translation_date)) + return 0.0 \ No newline at end of file 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 d8d00efa967..c206ba276a6 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) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 0caf1cdf7ac..c0fe3a62314 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -481,7 +481,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,