diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 8927036b82c..98383810c1a 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -7,7 +7,7 @@ "description": "Heads (or groups) against which Accounting Entries are made and balances are maintained.", "docstatus": 0, "doctype": "DocType", - "document_type": "Master", + "document_type": "Setup", "fields": [ { "allow_on_submit": 0, @@ -166,6 +166,30 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:doc.is_group==0", + "fieldname": "account_currency", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Currency", + "no_copy": 0, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 16d25010e38..079bc361cac 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -30,6 +30,7 @@ class Account(Document): self.validate_warehouse_account() self.validate_frozen_accounts_modifier() self.validate_balance_must_be_debit_or_credit() + self.validate_account_currency() def validate_parent(self): """Fetch Parent Details and validate parent account""" @@ -88,6 +89,14 @@ class Account(Document): frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'")) elif account_balance < 0 and self.balance_must_be == "Debit": frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'")) + + def validate_account_currency(self): + if not self.account_currency: + self.account_currency = frappe.db.get_value("Company", self.company, "default_currency") + + elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"): + if frappe.db.get_value("GL Entry", {"account": self.name}): + frappe.throw(_("Currency can not be changed after making entries using some other currency")) def convert_group_to_ledger(self): if self.check_if_child_exists(): diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index ce3a7fddb75..bfb5240fa82 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -36,7 +36,8 @@ def create_charts(chart_name, company): "is_group": is_group, "root_type": root_type, "report_type": report_type, - "account_type": child.get("account_type") + "account_type": child.get("account_type"), + "account_currency": frappe.db.get_value("Company", company, "default_currency") }) if root_account: diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 2a3feda79b1..83516dacb9d 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -9,36 +9,39 @@ def _make_test_records(verbose): accounts = [ # [account_name, parent_account, is_group] - ["_Test Account Bank Account", "Bank Accounts", 0, "Bank"], + ["_Test Bank", "Bank Accounts", 0, "Bank", None], + ["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"], + ["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"], - ["_Test Account Stock Expenses", "Direct Expenses", 1, None], - ["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable"], - ["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax"], - ["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable"], - ["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment"], + ["_Test Account Stock Expenses", "Direct Expenses", 1, None, None], + ["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None], + ["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None], + ["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None], + ["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None], + ["_Test Account Tax Assets", "Current Assets", 1, None, None], + ["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None], + ["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None], - ["_Test Account Tax Assets", "Current Assets", 1, None], - ["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax"], - ["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax"], + ["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None], - ["_Test Account Reserves and Surplus", "Current Liabilities", 0, None], - - ["_Test Account Cost for Goods Sold", "Expenses", 0, None], - ["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax"], - ["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax"], - ["_Test Account S&H Education Cess", "_Test Account Tax Assets", 0, "Tax"], - ["_Test Account CST", "Direct Expenses", 0, "Tax"], - ["_Test Account Discount", "Direct Expenses", 0, None], - ["_Test Write Off", "Indirect Expenses", 0, None], + ["_Test Account Cost for Goods Sold", "Expenses", 0, None, None], + ["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None], + ["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None], + ["_Test Account S&H Education Cess", "_Test Account Tax Assets", 0, "Tax", None], + ["_Test Account CST", "Direct Expenses", 0, "Tax", None], + ["_Test Account Discount", "Direct Expenses", 0, None, None], + ["_Test Write Off", "Indirect Expenses", 0, None, None], # related to Account Inventory Integration - ["_Test Account Stock In Hand", "Current Assets", 0, None], - ["_Test Account Fixed Assets", "Current Assets", 0, None], + ["_Test Account Stock In Hand", "Current Assets", 0, None, None], + ["_Test Account Fixed Assets", "Current Assets", 0, None, None], # Receivable / Payable Account - ["_Test Receivable", "Current Assets", 0, "Receivable"], - ["_Test Payable", "Current Liabilities", 0, "Payable"], + ["_Test Receivable", "Current Assets", 0, "Receivable", None], + ["_Test Payable", "Current Liabilities", 0, "Payable", None], + ["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"], + ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"] ] for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"]]: @@ -48,7 +51,8 @@ def _make_test_records(verbose): "parent_account": parent_account + " - " + abbr, "company": company, "is_group": is_group, - "account_type": account_type - } for account_name, parent_account, is_group, account_type in accounts]) + "account_type": account_type, + "account_currency": currency + } for account_name, parent_account, is_group, account_type, currency in accounts]) return test_objects diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 521e3baf0af..1b27ede3f31 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -156,7 +156,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Debit Amt", + "label": "Debit Amount", "no_copy": 0, "oldfieldname": "debit", "oldfieldtype": "Currency", @@ -180,7 +180,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Credit Amt", + "label": "Credit Amount", "no_copy": 0, "oldfieldname": "credit", "oldfieldtype": "Currency", @@ -194,6 +194,75 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "account_currency", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Account Currency", + "no_copy": 0, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "debit_in_account_currency", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Debit Amount in Account Currency", + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "credit_in_account_currency", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Credit Amount in Account Currency", + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index c4596284826..dbaf5901daf 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -10,6 +10,8 @@ from frappe import _ from frappe.model.document import Document class CustomerFrozen(frappe.ValidationError): pass +class InvalidCurrency(frappe.ValidationError): pass +class InvalidAccountCurrency(frappe.ValidationError): pass class GLEntry(Document): def validate(self): @@ -20,6 +22,7 @@ class GLEntry(Document): self.check_pl_account() self.validate_cost_center() self.validate_party() + self.validate_currency() def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'): self.validate_account_details(adv_adj) @@ -98,6 +101,25 @@ class GLEntry(Document): if not frozen_accounts_modifier in frappe.get_roles(): if frappe.db.get_value(self.party_type, self.party, "is_frozen"): frappe.throw("{0} {1} is frozen".format(self.party_type, self.party), CustomerFrozen) + + def validate_currency(self): + company_currency = frappe.db.get_value("Company", self.company, "default_currency") + account_currency = frappe.db.get_value("Account", self.account, "account_currency") or company_currency + + if not self.account_currency: + self.account_currency = company_currency + if account_currency != self.account_currency: + frappe.throw(_("Accounting Entry for {0} can only be made in currency: {1}") + .format(self.account, (account_currency or company_currency)), InvalidAccountCurrency) + + + if self.party_type and self.party: + party_account_currency = frappe.db.get_value(self.party_type, self.party, "party_account_currency") \ + or company_currency + + if party_account_currency != self.account_currency: + frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") + .format(self.party_type, self.party, party_account_currency), InvalidAccountCurrency) def validate_balance_type(account, adv_adj=False): if not adv_adj and account: @@ -131,17 +153,18 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga party_condition = "" # get final outstanding amt - bal = flt(frappe.db.sql("""select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + bal = flt(frappe.db.sql(""" + select sum(ifnull(debit_in_account_currency, 0)) - sum(ifnull(credit_in_account_currency, 0)) from `tabGL Entry` where against_voucher_type=%s and against_voucher=%s and account = %s {0}""".format(party_condition), (against_voucher_type, against_voucher, account))[0][0] or 0.0) - + if against_voucher_type == 'Purchase Invoice': bal = -bal elif against_voucher_type == "Journal Entry": against_voucher_amount = flt(frappe.db.sql(""" - select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + select sum(ifnull(debit_in_account_currency, 0)) - sum(ifnull(credit_in_account_currency, 0)) from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s and account = %s and ifnull(against_voucher, '') = '' {0}""" .format(party_condition), (against_voucher, account))[0][0]) diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index 383409cd3bf..146d084a11b 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -11,7 +11,7 @@ class TestGLEntry(unittest.TestCase): frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 100, "_Test Cost Center - _TC", submit=False) + "_Test Bank - _TC", 100, "_Test Cost Center - _TC", submit=False) jv.get("accounts")[0].debit = 100.01 jv.flags.ignore_validate = True diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 1e77422a250..383b4b620d5 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -2,8 +2,54 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.accounts"); +frappe.provide("erpnext.journal_entry"); frappe.require("assets/erpnext/js/utils.js"); +frappe.ui.form.on("Journal Entry", { + refresh: function(frm) { + erpnext.toggle_naming_series(); + cur_frm.cscript.voucher_type(frm.doc); + + if(frm.doc.docstatus==1) { + cur_frm.add_custom_button(__('View Ledger'), function() { + frappe.route_options = { + "voucher_no": frm.doc.name, + "from_date": frm.doc.posting_date, + "to_date": frm.doc.posting_date, + "company": frm.doc.company, + group_by_voucher: 0 + }; + frappe.set_route("query-report", "General Ledger"); + }, "icon-table"); + } + + // hide /unhide fields based on currency + erpnext.journal_entry.toggle_fields_based_on_currency(frm); + }, + + multi_currency: function(frm) { + erpnext.journal_entry.toggle_fields_based_on_currency(frm); + } +}) + +erpnext.journal_entry.toggle_fields_based_on_currency = function(frm) { + var fields = ["currency_section", "account_currency", "exchange_rate", "debit", "credit"]; + + var grid = frm.get_field("accounts").grid; + if(grid) grid.set_column_disp(fields, frm.doc.multi_currency); + + // dynamic label + var field_label_map = { + "debit_in_account_currency": "Debit", + "credit_in_account_currency": "Credit" + }; + + $.each(field_label_map, function (fieldname, label) { + var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name); + df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label; + }) +} + erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ onload: function() { this.load_defaults(); @@ -29,17 +75,27 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ setup_queries: function() { var me = this; - - $.each(["account", "cost_center"], function(i, fieldname) { - me.frm.set_query(fieldname, "accounts", function() { - frappe.model.validate_missing(me.frm.doc, "company"); - return { - filters: { - company: me.frm.doc.company, - is_group: 0 - } - }; - }); + + me.frm.set_query("account", "accounts", function(doc, cdt, cdn) { + var filters = { + company: me.frm.doc.company, + is_group: 0 + }; + if(!doc.multi_currency) { + $.extend(filters, { + account_currency: frappe.get_doc(":Company", me.frm.doc.company).default_currency + }); + } + return { filters: filters }; + }); + + me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) { + return { + filters: { + company: me.frm.doc.company, + is_group: 0 + } + }; }); me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) { @@ -118,32 +174,39 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ reference_name: function(doc, cdt, cdn) { var d = frappe.get_doc(cdt, cdn); - if (d.reference_type==="Purchase Invoice" && !flt(d.debit)) { - this.get_outstanding('Purchase Invoice', d.reference_name, d); - } - if (d.reference_type==="Sales Invoice" && !flt(d.credit)) { - this.get_outstanding('Sales Invoice', d.reference_name, d); - } - if (d.reference_type==="Journal Entry" && !flt(d.credit) && !flt(d.debit)) { - this.get_outstanding('Journal Entry', d.reference_name, d); + if(d.reference_name) { + if (d.reference_type==="Purchase Invoice" && !flt(d.debit)) { + this.get_outstanding('Purchase Invoice', d.reference_name, doc.company, d); + } + if (d.reference_type==="Sales Invoice" && !flt(d.credit)) { + this.get_outstanding('Sales Invoice', d.reference_name, doc.company, d); + } + if (d.reference_type==="Journal Entry" && !flt(d.credit) && !flt(d.debit)) { + this.get_outstanding('Journal Entry', d.reference_name, doc.company, d); + } } }, - get_outstanding: function(doctype, docname, child) { + get_outstanding: function(doctype, docname, company, child) { var me = this; var args = { "doctype": doctype, "docname": docname, "party": child.party, - "account": child.account + "account": child.account, + "account_currency": child.account_currency, + "company": company } - return this.frm.call({ - child: child, - method: "get_outstanding", + return frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_outstanding", args: { args: args}, callback: function(r) { - cur_frm.cscript.update_totals(me.frm.doc); + if(r.message) { + $.each(r.message, function(field, value) { + frappe.model.set_value(child.doctype, child.name, field, value); + }) + } } }); }, @@ -161,35 +224,20 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ // set difference if(doc.difference) { if(doc.difference > 0) { + row.credit_in_account_currency = doc.difference; row.credit = doc.difference; } else { + row.debit_in_account_currency = -doc.difference; row.debit = -doc.difference; } } + cur_frm.cscript.update_totals(doc); }, }); cur_frm.script_manager.make(erpnext.accounts.JournalEntry); -cur_frm.cscript.refresh = function(doc) { - erpnext.toggle_naming_series(); - cur_frm.cscript.voucher_type(doc); - - if(doc.docstatus==1) { - cur_frm.add_custom_button(__('View Ledger'), function() { - frappe.route_options = { - "voucher_no": doc.name, - "from_date": doc.posting_date, - "to_date": doc.posting_date, - "company": doc.company, - group_by_voucher: 0 - }; - frappe.set_route("query-report", "General Ledger"); - }, "icon-table"); - } -} - cur_frm.cscript.company = function(doc, cdt, cdn) { cur_frm.refresh_fields(); erpnext.get_fiscal_year(doc.company, doc.posting_date); @@ -201,10 +249,10 @@ cur_frm.cscript.posting_date = function(doc, cdt, cdn){ cur_frm.cscript.update_totals = function(doc) { var td=0.0; var tc =0.0; - var el = doc.accounts || []; - for(var i in el) { - td += flt(el[i].debit, precision("debit", el[i])); - tc += flt(el[i].credit, precision("credit", el[i])); + var accounts = doc.accounts || []; + for(var i in accounts) { + td += flt(accounts[i].debit, precision("debit", accounts[i])); + tc += flt(accounts[i].credit, precision("credit", accounts[i])); } var doc = locals[doc.doctype][doc.name]; doc.total_debit = td; @@ -213,32 +261,12 @@ cur_frm.cscript.update_totals = function(doc) { refresh_many(['total_debit','total_credit','difference']); } -cur_frm.cscript.debit = function(doc,dt,dn) { cur_frm.cscript.update_totals(doc); } -cur_frm.cscript.credit = function(doc,dt,dn) { cur_frm.cscript.update_totals(doc); } - cur_frm.cscript.get_balance = function(doc,dt,dn) { cur_frm.cscript.update_totals(doc); return $c_obj(cur_frm.doc, 'get_balance', '', function(r, rt){ cur_frm.refresh(); }); } -// Get balance -// ----------- - -cur_frm.cscript.account = function(doc,dt,dn) { - var d = locals[dt][dn]; - if(d.account) { - return frappe.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type", - args: {account: d.account, date: doc.posting_date}, - callback: function(r) { - $.extend(d, r.message); - refresh_field('balance', d.name, 'accounts'); - refresh_field('party_type', d.name, 'accounts'); - } - }); - } -} cur_frm.cscript.validate = function(doc,cdt,cdn) { cur_frm.cscript.update_totals(doc); @@ -303,18 +331,63 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { } } -frappe.ui.form.on("Journal Entry Account", "party", function(frm, cdt, cdn) { - var d = frappe.get_doc(cdt, cdn); - if(!d.account && d.party_type && d.party) { - return frm.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance", - child: d, - args: { - company: frm.doc.company, - party_type: d.party_type, - party: d.party - } - }); +frappe.ui.form.on("Journal Entry Account", { + party: function(frm, cdt, cdn) { + var d = frappe.get_doc(cdt, cdn); + if(!d.account && d.party_type && d.party) { + return frm.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance", + child: d, + args: { + company: frm.doc.company, + party_type: d.party_type, + party: d.party + } + }); + } + }, + + account: function(frm, dt, dn) { + var d = locals[dt][dn]; + if(d.account) { + return frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type", + args: { + account: d.account, + date: frm.doc.posting_date, + company: frm.doc.company, + debit: flt(d.debit_in_account_currency), + credit: flt(d.credit_in_account_currency), + exchange_rate: d.exchange_rate + }, + callback: function(r) { + if(r.message) { + $.extend(d, r.message); + refresh_field('accounts'); + } + } + }); + } + }, + + debit_in_account_currency: function(frm, cdt, cdn) { + erpnext.journal_entry.set_debit_credit_in_company_currency(frm, cdt, cdn); + }, + + credit_in_account_currency: function(frm, cdt, cdn) { + erpnext.journal_entry.set_debit_credit_in_company_currency(frm, cdt, cdn); + }, + + debit: function(frm, dt, dn) { + cur_frm.cscript.update_totals(frm.doc); + }, + + credit: function(frm, dt, dn) { + cur_frm.cscript.update_totals(frm.doc); + }, + + exchange_rate: function(frm, cdt, cdn) { + erpnext.journal_entry.set_debit_credit_in_company_currency(frm, cdt, cdn); } }) @@ -322,3 +395,41 @@ frappe.ui.form.on("Journal Entry Account", "accounts_remove", function(frm) { cur_frm.cscript.update_totals(frm.doc); }); +erpnext.journal_entry.set_debit_credit_in_company_currency = function(frm, cdt, cdn) { + erpnext.journal_entry.set_exchange_rate(frm, cdt, cdn); + + var row = locals[cdt][cdn]; + + frappe.model.set_value(cdt, cdn, "debit", + flt(flt(row.debit_in_account_currency)*row.exchange_rate), precision("debit", row)); + frappe.model.set_value(cdt, cdn, "credit", + flt(flt(row.credit_in_account_currency)*row.exchange_rate), precision("credit", row)); +} + +erpnext.journal_entry.set_exchange_rate = function(frm, cdt, cdn) { + var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + var row = locals[cdt][cdn]; + + if(row.account_currency == company_currency || !frm.doc.multi_currency) { + frappe.model.set_value(cdt, cdn, "exchange_rate", 1); + } else if (!row.exchange_rate || row.account_type == "Bank") { + frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", + args: { + account: row.account, + account_currency: row.account_currency, + company: frm.doc.company, + reference_type: cstr(row.reference_type), + reference_name: cstr(row.reference_name), + debit: flt(row.debit_in_account_currency), + credit: flt(row.credit_in_account_currency), + exchange_rate: row.exchange_rate + }, + callback: function(r) { + if(r.message) { + frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); + } + } + }) + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index a01bf42c2cc..2b025e6f33d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -1,1078 +1,1100 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "creation": "2013-03-25 10:53:52", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "naming_series:", + "creation": "2013-03-25 10:53:52", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "entry_type_and_date", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "", - "no_copy": 0, - "options": "icon-flag", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "entry_type_and_date", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "", + "no_copy": 0, + "options": "icon-flag", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "default": "", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Title", - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "default": "", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Title", + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "Journal Entry", - "fieldname": "voucher_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Entry Type", - "no_copy": 0, - "oldfieldname": "voucher_type", - "oldfieldtype": "Select", - "options": "Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "Journal Entry", + "fieldname": "voucher_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Entry Type", + "no_copy": 0, + "oldfieldname": "voucher_type", + "oldfieldtype": "Select", + "options": "Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Series", - "no_copy": 1, - "oldfieldname": "naming_series", - "oldfieldtype": "Select", - "options": "JV-", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Series", + "no_copy": 1, + "oldfieldname": "naming_series", + "oldfieldtype": "Select", + "options": "JV-", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "oldfieldtype": "Column Break", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, "width": "50%" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Posting Date", - "no_copy": 1, - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Posting Date", + "no_copy": 1, + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "2_add_edit_gl_entries", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "", - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "icon-table", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "2_add_edit_gl_entries", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "", + "no_copy": 0, + "oldfieldtype": "Section Break", + "options": "icon-table", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "fieldname": "accounts", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Accounting Entries", - "no_copy": 0, - "oldfieldname": "entries", - "oldfieldtype": "Table", - "options": "Journal Entry Account", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "fieldname": "accounts", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Accounting Entries", + "no_copy": 0, + "oldfieldname": "entries", + "oldfieldtype": "Table", + "options": "Journal Entry Account", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "section_break99", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "section_break99", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "", - "fieldname": "cheque_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 1, - "label": "Reference Number", - "no_copy": 1, - "oldfieldname": "cheque_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "description": "", + "fieldname": "cheque_no", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 1, + "label": "Reference Number", + "no_copy": 1, + "oldfieldname": "cheque_no", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "cheque_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Date", - "no_copy": 1, - "oldfieldname": "cheque_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "cheque_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Reference Date", + "no_copy": 1, + "oldfieldname": "cheque_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "user_remark", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "User Remark", - "no_copy": 1, - "oldfieldname": "user_remark", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "user_remark", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "User Remark", + "no_copy": 1, + "oldfieldname": "user_remark", + "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break99", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break99", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "total_debit", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 1, - "label": "Total Debit", - "no_copy": 1, - "oldfieldname": "total_debit", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "total_debit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 1, + "label": "Total Debit", + "no_copy": 1, + "oldfieldname": "total_debit", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "total_credit", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Total Credit", - "no_copy": 1, - "oldfieldname": "total_credit", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "total_credit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Total Credit", + "no_copy": 1, + "oldfieldname": "total_credit", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "difference", - "fieldname": "difference", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Difference (Dr - Cr)", - "no_copy": 1, - "oldfieldname": "difference", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "difference", + "fieldname": "difference", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Difference (Dr - Cr)", + "no_copy": 1, + "oldfieldname": "difference", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "difference", - "fieldname": "get_balance", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Make Difference Entry", - "no_copy": 0, - "oldfieldtype": "Button", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "difference", + "fieldname": "get_balance", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Make Difference Entry", + "no_copy": 0, + "oldfieldtype": "Button", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "total_amount", - "fieldtype": "Currency", - "hidden": 1, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Total Amount", - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "multi_currency", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Multi Currency", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "total_amount_in_words", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Total Amount in Words", - "no_copy": 1, - "permlevel": 0, - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "total_amount", + "fieldtype": "Currency", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Total Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "reference", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference", - "no_copy": 0, - "options": "icon-pushpin", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "total_amount_in_words", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Total Amount in Words", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "clearance_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Clearance Date", - "no_copy": 1, - "oldfieldname": "clearance_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "fieldname": "reference", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Reference", + "no_copy": 0, + "options": "icon-pushpin", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "", - "fieldname": "remark", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Remark", - "no_copy": 1, - "oldfieldname": "remark", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "clearance_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Clearance Date", + "no_copy": 1, + "oldfieldname": "clearance_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break98", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "description": "", + "fieldname": "remark", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Remark", + "no_copy": 1, + "oldfieldname": "remark", + "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "bill_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Bill No", - "no_copy": 0, - "oldfieldname": "bill_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break98", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "bill_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Bill Date", - "no_copy": 0, - "oldfieldname": "bill_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "bill_no", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Bill No", + "no_copy": 0, + "oldfieldname": "bill_no", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "due_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Due Date", - "no_copy": 0, - "oldfieldname": "due_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "bill_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Bill Date", + "no_copy": 0, + "oldfieldname": "bill_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", - "fieldname": "write_off", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off", - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Due Date", + "no_copy": 0, + "oldfieldname": "due_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "Accounts Receivable", - "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", - "fieldname": "write_off_based_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off Based On", - "no_copy": 0, - "options": "Accounts Receivable\nAccounts Payable", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", + "fieldname": "write_off", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write Off", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", - "fieldname": "get_outstanding_invoices", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Get Outstanding Invoices", - "no_copy": 0, - "options": "get_outstanding_invoices", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "Accounts Receivable", + "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", + "fieldname": "write_off_based_on", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write Off Based On", + "no_copy": 0, + "options": "Accounts Receivable\nAccounts Payable", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_30", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", + "fieldname": "get_outstanding_invoices", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Get Outstanding Invoices", + "no_copy": 0, + "options": "get_outstanding_invoices", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", - "fieldname": "write_off_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Write Off Amount", - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_30", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "printing_settings", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Printing Settings", - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:doc.voucher_type == 'Write Off Entry'", + "fieldname": "write_off_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write Off Amount", + "no_copy": 0, + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "pay_to_recd_from", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Pay To / Recd From", - "no_copy": 1, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "fieldname": "printing_settings", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Printing Settings", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_35", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "pay_to_recd_from", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Pay To / Recd From", + "no_copy": 1, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "fieldname": "letter_head", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Letter Head", - "no_copy": 0, - "options": "Letter Head", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_35", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "fieldname": "select_print_heading", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Print Heading", - "no_copy": 1, - "oldfieldname": "select_print_heading", - "oldfieldtype": "Link", - "options": "Print Heading", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "fieldname": "letter_head", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Letter Head", + "no_copy": 0, + "options": "Letter Head", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "addtional_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "More Information", - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "icon-file-text", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Print Heading", + "no_copy": 1, + "oldfieldname": "select_print_heading", + "oldfieldtype": "Link", + "options": "Print Heading", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Company", - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "fieldname": "addtional_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "More Information", + "no_copy": 0, + "oldfieldtype": "Section Break", + "options": "icon-file-text", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Fiscal Year", - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Select", - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Company", + "no_copy": 0, + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "fiscal_year", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Fiscal Year", + "no_copy": 0, + "oldfieldname": "fiscal_year", + "oldfieldtype": "Select", + "options": "Fiscal Year", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "oldfieldtype": "Column Break", + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, "width": "50%" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "No", - "description": "", - "fieldname": "is_opening", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Is Opening", - "no_copy": 0, - "oldfieldname": "is_opening", - "oldfieldtype": "Select", - "options": "No\nYes", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "No", + "description": "", + "fieldname": "is_opening", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 1, + "in_list_view": 0, + "label": "Is Opening", + "no_copy": 0, + "oldfieldname": "is_opening", + "oldfieldtype": "Select", + "options": "No\nYes", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:inList([\"Credit Note\", \"Debit Note\"], doc.voucher_type)", - "fieldname": "stock_entry", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Stock Entry", - "no_copy": 0, - "options": "Stock Entry", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:inList([\"Credit Note\", \"Debit Note\"], doc.voucher_type)", + "fieldname": "stock_entry", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Stock Entry", + "no_copy": 0, + "options": "Stock Entry", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "in_filter": 0, - "in_list_view": 0, - "label": "Amended From", - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Link", - "options": "Journal Entry", - "permlevel": 0, - "print_hide": 1, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "in_filter": 0, + "in_list_view": 0, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Link", + "options": "Journal Entry", + "permlevel": 0, + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-file-text", - "idx": 1, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "modified": "2015-09-11 12:19:50.635624", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Journal Entry", - "owner": "Administrator", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-file-text", + "idx": 1, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "modified": "2015-09-11 12:20:50.635624", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Journal Entry", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "apply_user_permissions": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "apply_user_permissions": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Auditor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Auditor", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 } - ], - "read_only": 0, - "read_only_onload": 1, - "search_fields": "voucher_type,posting_date, due_date, cheque_no", - "sort_field": "modified", - "sort_order": "DESC", + ], + "read_only": 0, + "read_only_onload": 1, + "search_fields": "voucher_type,posting_date, due_date, cheque_no", + "sort_field": "modified", + "sort_order": "DESC", "title_field": "title" -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index f5f22dccac2..cb12969112c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -3,11 +3,11 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, date_diff +from frappe.utils import cstr, flt, fmt_money, formatdate from frappe import msgprint, _, scrub -from erpnext.setup.utils import get_company_currency from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.utils import get_balance_on +from erpnext.setup.utils import get_company_currency class JournalEntry(AccountsController): @@ -26,6 +26,7 @@ class JournalEntry(AccountsController): self.validate_party() self.validate_cheque_info() self.validate_entries_for_advance() + self.validate_multi_currency() self.validate_debit_and_credit() self.validate_against_jv() self.validate_reference_doc() @@ -35,6 +36,7 @@ class JournalEntry(AccountsController): self.validate_expense_claim() self.validate_credit_debit_note() self.validate_empty_accounts_table() + self.set_account_and_party_balance() self.set_title() def on_submit(self): @@ -144,6 +146,7 @@ class JournalEntry(AccountsController): self.reference_totals = {} self.reference_types = {} + self.reference_parties = {} for d in self.get("accounts"): if not d.reference_type: @@ -151,8 +154,8 @@ class JournalEntry(AccountsController): if not d.reference_name: d.reference_type = None if d.reference_type and d.reference_name and (d.reference_type in field_dict.keys()): - dr_or_cr = "credit" if d.reference_type in ("Sales Order", "Sales Invoice") \ - else "debit" + dr_or_cr = "credit_in_account_currency" \ + if d.reference_type in ("Sales Order", "Sales Invoice") else "debit_in_account_currency" # check debit or credit type Sales / Purchase Order if d.reference_type=="Sales Order" and flt(d.debit) > 0: @@ -166,6 +169,8 @@ class JournalEntry(AccountsController): self.reference_totals[d.reference_name] = 0.0 self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr)) self.reference_types[d.reference_name] = d.reference_type + if d.party_type and d.party: + self.reference_parties[d.reference_name] = [d.party_type, d.party] against_voucher = frappe.db.get_value(d.reference_type, d.reference_name, [scrub(dt) for dt in field_dict.get(d.reference_type)]) @@ -191,23 +196,31 @@ class JournalEntry(AccountsController): """Validate totals, stopped and docstatus for orders""" for reference_name, total in self.reference_totals.iteritems(): reference_type = self.reference_types[reference_name] + party_type, party = self.reference_parties.get(reference_name) if reference_type in ("Sales Order", "Purchase Order"): - voucher_properties = frappe.db.get_value(reference_type, reference_name, - ["docstatus", "per_billed", "status", "advance_paid", "base_grand_total"]) + order = frappe.db.get_value(reference_type, reference_name, + ["docstatus", "per_billed", "status", "advance_paid", + "base_grand_total", "grand_total", "currency"], as_dict=1) - if voucher_properties[0] != 1: + if order.docstatus != 1: frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name)) - if flt(voucher_properties[1]) >= 100: + if flt(order.per_billed) >= 100: frappe.throw(_("{0} {1} is fully billed").format(reference_type, reference_name)) - if cstr(voucher_properties[2]) == "Stopped": + if cstr(order.status) == "Stopped": frappe.throw(_("{0} {1} is stopped").format(reference_type, reference_name)) - if flt(voucher_properties[4]) < (flt(voucher_properties[3]) + total): + party_account_currency = frappe.db.get_value(party_type, party, "party_account_currency") + if party_account_currency == self.company_currency: + voucher_total = order.base_grand_total + else: + voucher_total = order.grand_total + + if flt(voucher_total) < (flt(order.advance_paid) + total): frappe.throw(_("Advance paid against {0} {1} cannot be greater \ - than Grand Total {2}").format(reference_type, reference_name, voucher_properties[4])) + than Grand Total {2}").format(reference_type, reference_name, voucher_total)) def validate_invoices(self): """Validate totals and docstatus for invoices""" @@ -215,15 +228,15 @@ class JournalEntry(AccountsController): reference_type = self.reference_types[reference_name] if reference_type in ("Sales Invoice", "Purchase Invoice"): - voucher_properties = frappe.db.get_value(reference_type, reference_name, - ["docstatus", "outstanding_amount"]) + invoice = frappe.db.get_value(reference_type, reference_name, + ["docstatus", "outstanding_amount"], as_dict=1) - if voucher_properties[0] != 1: + if invoice.docstatus != 1: frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name)) - if total and flt(voucher_properties[1]) < total: - frappe.throw(_("Payment against {0} {1} cannot be greater \ - than Outstanding Amount {2}").format(reference_type, reference_name, voucher_properties[1])) + if total and flt(invoice.outstanding_amount) < total: + frappe.throw(_("Payment against {0} {1} cannot be greater than Outstanding Amount {2}") + .format(reference_type, reference_name, invoice.outstanding_amount)) def set_against_account(self): accounts_debited, accounts_credited = [], [] @@ -237,13 +250,12 @@ class JournalEntry(AccountsController): def validate_debit_and_credit(self): self.total_debit, self.total_credit, self.difference = 0, 0, 0 - for d in self.get("accounts"): if d.debit and d.credit: frappe.throw(_("You cannot credit and debit same account at the same time")) - self.total_debit = flt(self.total_debit) + flt(d.debit, self.precision("debit", "accounts")) - self.total_credit = flt(self.total_credit) + flt(d.credit, self.precision("credit", "accounts")) + self.total_debit = flt(self.total_debit) + flt(d.debit, d.precision("debit")) + self.total_credit = flt(self.total_credit) + flt(d.credit, d.precision("credit")) self.difference = flt(self.total_debit, self.precision("total_debit")) - \ flt(self.total_credit, self.precision("total_credit")) @@ -252,6 +264,41 @@ class JournalEntry(AccountsController): frappe.throw(_("Total Debit must be equal to Total Credit. The difference is {0}") .format(self.difference)) + def validate_multi_currency(self): + alternate_currency = [] + for d in self.get("accounts"): + account = frappe.db.get_value("Account", d.account, ["account_currency", "account_type"], as_dict=1) + d.account_currency = account.account_currency or self.company_currency + d.account_type = account.account_type + + if d.account_currency!=self.company_currency and d.account_currency not in alternate_currency: + alternate_currency.append(d.account_currency) + + if alternate_currency: + if not self.multi_currency: + frappe.throw(_("Please check Multi Currency option to allow accounts with other currency")) + + if len(alternate_currency) > 1: + frappe.throw(_("Only one alternate currency can be used in a single Journal Entry")) + + self.set_exchange_rate() + + for d in self.get("accounts"): + d.debit = flt(flt(d.debit_in_account_currency)*flt(d.exchange_rate), d.precision("debit")) + d.credit = flt(flt(d.credit_in_account_currency)*flt(d.exchange_rate), d.precision("credit")) + + def set_exchange_rate(self): + for d in self.get("accounts"): + if d.account_currency == self.company_currency: + d.exchange_rate = 1 + elif not d.exchange_rate or d.account_type=="Bank" 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, d.reference_name, d.debit, d.credit, d.exchange_rate) + + if not d.exchange_rate: + frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx)) + def create_remarks(self): r = [] if self.cheque_no: @@ -260,15 +307,13 @@ class JournalEntry(AccountsController): else: msgprint(_("Please enter Reference date"), raise_exception=frappe.MandatoryError) - company_currency = get_company_currency(self.company) - for d in self.get('accounts'): if d.reference_type=="Sales Invoice" and d.credit: - r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ + r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = self.company_currency), \ d.reference_name)) if d.reference_type=="Sales Order" and d.credit: - r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ + r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = self.company_currency), \ d.reference_name)) if d.reference_type == "Purchase Invoice" and d.debit: @@ -276,11 +321,11 @@ class JournalEntry(AccountsController): from `tabPurchase Invoice` where name=%s""", d.reference_name) if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \ not in ['na', 'not applicable', 'none']: - r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=company_currency), bill_no[0][0], + r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=self.company_currency), bill_no[0][0], bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d')))) if d.reference_type == "Purchase Order" and d.debit: - r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ + r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = self.company_currency), \ d.reference_name)) if self.user_remark: @@ -301,10 +346,9 @@ class JournalEntry(AccountsController): self.set_total_amount(d.debit or d.credit) def set_total_amount(self, amt): - company_currency = get_company_currency(self.company) self.total_amount = amt from frappe.utils import money_in_words - self.total_amount_in_words = money_in_words(amt, company_currency) + self.total_amount_in_words = money_in_words(amt, self.company_currency) def make_gl_entries(self, cancel=0, adv_adj=0): from erpnext.accounts.general_ledger import make_gl_entries @@ -318,8 +362,11 @@ class JournalEntry(AccountsController): "party_type": d.party_type, "party": d.party, "against": d.against_account, - "debit": flt(d.debit, self.precision("debit", "accounts")), - "credit": flt(d.credit, self.precision("credit", "accounts")), + "debit": flt(d.debit, d.precision("debit")), + "credit": flt(d.credit, d.precision("credit")), + "account_currency": d.account_currency, + "debit_in_account_currency": flt(d.debit_in_account_currency, d.precision("debit_in_account_currency")), + "credit_in_account_currency": flt(d.credit_in_account_currency, d.precision("credit_in_account_currency")), "against_voucher_type": d.reference_type, "against_voucher": d.reference_name, "remarks": self.remark, @@ -338,21 +385,21 @@ class JournalEntry(AccountsController): diff = flt(self.difference, self.precision("difference")) # If any row without amount, set the diff on that row - for d in self.get('accounts'): - if not d.credit and not d.debit and diff != 0: - if diff>0: - d.credit = diff - elif diff<0: - d.debit = diff - flag = 1 + if diff: + for d in self.get('accounts'): + if not d.credit_in_account_currency and not d.debit_in_account_currency and diff != 0: + blank_row = d - # Set the diff in a new row - if flag == 0 and diff != 0: - jd = self.append('accounts', {}) + if not blank_row: + blank_row = self.append('accounts', {}) + + blank_row.exchange_rate = 1 if diff>0: - jd.credit = abs(diff) + blank_row.credit_in_account_currency = diff + blank_row.credit = diff elif diff<0: - jd.debit = abs(diff) + blank_row.debit_in_account_currency = abs(diff) + blank_row.debit = abs(diff) self.validate_debit_and_credit() @@ -427,6 +474,11 @@ class JournalEntry(AccountsController): if not self.get('accounts'): frappe.throw("Accounts table cannot be blank.") + def set_account_and_party_balance(self): + for d in self.get("accounts"): + d.account_balance = get_balance_on(account=d.account, date=self.posting_date) + d.party_balance = get_balance_on(party_type=d.party_type, party=d.party, date=self.posting_date) + @frappe.whitelist() def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None): from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account @@ -446,9 +498,12 @@ def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None): account = frappe.db.get_value("Account", {"company": company, "account_type": "Cash", "is_group": 0}) if account: + account_details = frappe.db.get_value("Account", account, ["account_currency", "account_type"], as_dict=1) return { "account": account, - "balance": get_balance_on(account) + "balance": get_balance_on(account), + "account_currency": account_details.account_currency, + "account_type": account_details.account_type } @frappe.whitelist() @@ -456,21 +511,38 @@ def get_payment_entry_from_sales_invoice(sales_invoice): """Returns new Journal Entry document as dict for given Sales Invoice""" from erpnext.accounts.utils import get_balance_on si = frappe.get_doc("Sales Invoice", sales_invoice) + + # exchange rate + exchange_rate = get_exchange_rate(si.debit_to, si.party_account_currency, si.company, + si.doctype, si.name) + jv = get_payment_entry(si) jv.remark = 'Payment received against Sales Invoice {0}. {1}'.format(si.name, si.remarks) # credit customer - jv.get("accounts")[0].account = si.debit_to - jv.get("accounts")[0].party_type = "Customer" - jv.get("accounts")[0].party = si.customer - jv.get("accounts")[0].balance = get_balance_on(si.debit_to) - jv.get("accounts")[0].party_balance = get_balance_on(party=si.customer, party_type="Customer") - jv.get("accounts")[0].credit = si.outstanding_amount - jv.get("accounts")[0].reference_type = si.doctype - jv.get("accounts")[0].reference_name = si.name + row1 = jv.get("accounts")[0] + row1.account = si.debit_to + row1.account_currency = si.party_account_currency + row1.party_type = "Customer" + row1.party = si.customer + row1.balance = get_balance_on(si.debit_to) + row1.party_balance = get_balance_on(party=si.customer, party_type="Customer") + row1.credit_in_account_currency = si.outstanding_amount + row1.reference_type = si.doctype + row1.reference_name = si.name + row1.exchange_rate = exchange_rate + row1.account_type = "Receivable" if si.customer else "" # debit bank - jv.get("accounts")[1].debit = si.outstanding_amount + row2 = jv.get("accounts")[1] + if row2.account_currency == si.party_account_currency: + row2.debit_in_account_currency = si.outstanding_amount + else: + row2.debit_in_account_currency = si.outstanding_amount * exchange_rate + + # set multi currency check + if row1.account_currency != si.company_currency or row2.account_currency != si.company_currency: + jv.multi_currency = 1 return jv.as_dict() @@ -478,21 +550,38 @@ def get_payment_entry_from_sales_invoice(sales_invoice): def get_payment_entry_from_purchase_invoice(purchase_invoice): """Returns new Journal Entry document as dict for given Purchase Invoice""" pi = frappe.get_doc("Purchase Invoice", purchase_invoice) + + exchange_rate = get_exchange_rate(pi.debit_to, pi.party_account_currency, pi.company, + pi.doctype, pi.name) + jv = get_payment_entry(pi) jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks) + jv.exchange_rate = exchange_rate # credit supplier - jv.get("accounts")[0].account = pi.credit_to - jv.get("accounts")[0].party_type = "Supplier" - jv.get("accounts")[0].party = pi.supplier - jv.get("accounts")[0].balance = get_balance_on(pi.credit_to) - jv.get("accounts")[0].party_balance = get_balance_on(party=pi.supplier, party_type="Supplier") - jv.get("accounts")[0].debit = pi.outstanding_amount - jv.get("accounts")[0].reference_type = pi.doctype - jv.get("accounts")[0].reference_name = pi.name + row1 = jv.get("accounts")[0] + row1.account = pi.credit_to + row1.account_currency = pi.party_account_currency + row1.party_type = "Supplier" + row1.party = pi.supplier + row1.balance = get_balance_on(pi.credit_to) + row1.party_balance = get_balance_on(party=pi.supplier, party_type="Supplier") + row1.debit_in_account_currency = pi.outstanding_amount + row1.reference_type = pi.doctype + row1.reference_name = pi.name + row1.exchange_rate = exchange_rate + row1.account_type = "Payable" if pi.supplier else "" # credit bank - jv.get("accounts")[1].credit = pi.outstanding_amount + row2 = jv.get("accounts")[1] + if row2.account_currency == pi.party_account_currency: + row2.credit_in_account_currency = pi.outstanding_amount + else: + row2.credit_in_account_currency = pi.outstanding_amount * exchange_rate + + # set multi currency check + if row1.account_currency != pi.company_currency or row2.account_currency != pi.company_currency: + jv.multi_currency = 1 return jv.as_dict() @@ -501,6 +590,7 @@ def get_payment_entry_from_sales_order(sales_order): """Returns new Journal Entry document as dict for given Sales Order""" from erpnext.accounts.utils import get_balance_on from erpnext.accounts.party import get_party_account + so = frappe.get_doc("Sales Order", sales_order) if flt(so.per_billed, 2) != 0.0: @@ -508,23 +598,42 @@ def get_payment_entry_from_sales_order(sales_order): jv = get_payment_entry(so) jv.remark = 'Advance payment received against Sales Order {0}.'.format(so.name) - party_account = get_party_account(so.company, so.customer, "Customer") - amount = flt(so.base_grand_total) - flt(so.advance_paid) + party_account = get_party_account(so.company, so.customer, "Customer") + party_account_currency = frappe.db.get_value("Account", party_account, "account_currency") + + exchange_rate = get_exchange_rate(party_account, party_account_currency, so.company) + + if party_account_currency == so.company_currency: + amount = flt(so.base_grand_total) - flt(so.advance_paid) + else: + amount = flt(so.grand_total) - flt(so.advance_paid) # credit customer - jv.get("accounts")[0].account = party_account - jv.get("accounts")[0].party_type = "Customer" - jv.get("accounts")[0].party = so.customer - jv.get("accounts")[0].balance = get_balance_on(party_account) - jv.get("accounts")[0].party_balance = get_balance_on(party=so.customer, party_type="Customer") - jv.get("accounts")[0].credit = amount - jv.get("accounts")[0].reference_type = so.doctype - jv.get("accounts")[0].reference_name = so.name - jv.get("accounts")[0].is_advance = "Yes" + row1 = jv.get("accounts")[0] + row1.account = party_account + row1.account_currency = party_account_currency + row1.party_type = "Customer" + row1.party = so.customer + row1.balance = get_balance_on(party_account) + row1.party_balance = get_balance_on(party=so.customer, party_type="Customer") + row1.credit_in_account_currency = amount + row1.reference_type = so.doctype + row1.reference_name = so.name + row1.is_advance = "Yes" + row1.exchange_rate = exchange_rate + row1.account_type = "Receivable" # debit bank - jv.get("accounts")[1].debit = amount + row2 = jv.get("accounts")[1] + if row2.account_currency == party_account_currency: + row2.debit_in_account_currency = amount + else: + row2.debit_in_account_currency = amount * exchange_rate + + # set multi currency check + if row1.account_currency != so.company_currency or row2.account_currency != so.company_currency: + jv.multi_currency = 1 return jv.as_dict() @@ -540,23 +649,41 @@ def get_payment_entry_from_purchase_order(purchase_order): jv = get_payment_entry(po) jv.remark = 'Advance payment made against Purchase Order {0}.'.format(po.name) - party_account = get_party_account(po.company, po.supplier, "Supplier") - amount = flt(po.base_grand_total) - flt(po.advance_paid) + party_account = get_party_account(po.company, po.supplier, "Supplier") + party_account_currency = frappe.db.get_value("Account", party_account, "account_currency") + + exchange_rate = get_exchange_rate(party_account, party_account_currency, po.company) + + if party_account_currency == po.company_currency: + amount = flt(po.base_grand_total) - flt(po.advance_paid) + else: + amount = flt(po.grand_total) - flt(po.advance_paid) # credit customer - jv.get("accounts")[0].account = party_account - jv.get("accounts")[0].party_type = "Supplier" - jv.get("accounts")[0].party = po.supplier - jv.get("accounts")[0].balance = get_balance_on(party_account) - jv.get("accounts")[0].party_balance = get_balance_on(party=po.supplier, party_type="Supplier") - jv.get("accounts")[0].debit = amount - jv.get("accounts")[0].reference_type = po.doctype - jv.get("accounts")[0].reference_name = po.name - jv.get("accounts")[0].is_advance = "Yes" + row1 = jv.get("accounts")[0] + row1.account = party_account + row1.party_type = "Supplier" + row1.party = po.supplier + row1.balance = get_balance_on(party_account) + row1.party_balance = get_balance_on(party=po.supplier, party_type="Supplier") + row1.debit_in_account_currency = amount + row1.reference_type = po.doctype + row1.reference_name = po.name + row1.is_advance = "Yes" + row1.exchange_rate = exchange_rate + row1.account_type = "Payable" # debit bank - jv.get("accounts")[1].credit = amount + row2 = jv.get("accounts")[1] + if row2.account_currency == party_account_currency: + row2.credit_in_account_currency = amount + else: + row2.credit_in_account_currency = amount * exchange_rate + + # set multi currency check + if row1.account_currency != po.company_currency or row2.account_currency != po.company_currency: + jv.multi_currency = 1 return jv.as_dict() @@ -574,6 +701,10 @@ def get_payment_entry(doc): if bank_account: d2.account = bank_account["account"] d2.balance = bank_account["balance"] + d2.account_currency = bank_account["account_currency"] + d2.account_type = bank_account["account_type"] + d2.exchange_rate = get_exchange_rate(bank_account["account"], + bank_account["account_currency"], doc.company) return jv @@ -599,27 +730,37 @@ def get_outstanding(args): if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) args = eval(args) + company_currency = get_company_currency(args.get("company")) + if args.get("doctype") == "Journal Entry": condition = " and party=%(party)s" if args.get("party") else "" against_jv_amount = frappe.db.sql(""" - select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + select sum(ifnull(debit_in_account_currency, 0)) - sum(ifnull(credit_in_account_currency, 0)) from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0} and ifnull(reference_type, '')=''""".format(condition), args) against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0 + amount_field = "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency" return { - ("credit" if against_jv_amount > 0 else "debit"): abs(against_jv_amount) + amount_field: abs(against_jv_amount) } - elif args.get("doctype") == "Sales Invoice": - outstanding_amount = flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount")) + elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"): + invoice = frappe.db.get_value(args["doctype"], args["docname"], + ["outstanding_amount", "conversion_rate"], as_dict=1) + + exchange_rate = invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1 + + if args["doctype"] == "Sales Invoice": + amount_field = "credit_in_account_currency" \ + if flt(invoice.outstanding_amount) > 0 else "debit_in_account_currency" + else: + amount_field = "debit_in_account_currency" \ + if flt(invoice.outstanding_amount) > 0 else "credit_in_account_currency" + return { - ("credit" if outstanding_amount > 0 else "debit"): abs(outstanding_amount) - } - elif args.get("doctype") == "Purchase Invoice": - outstanding_amount = flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount")) - return { - ("debit" if outstanding_amount > 0 else "credit"): abs(outstanding_amount) + amount_field: abs(flt(invoice.outstanding_amount)), + "exchange_rate": exchange_rate } @frappe.whitelist() @@ -640,14 +781,58 @@ def get_party_account_and_balance(company, party_type, party): } @frappe.whitelist() -def get_account_balance_and_party_type(account, date): +def get_account_balance_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None): """Returns dict of account balance and party type to be set in Journal Entry on selection of account.""" if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) - account_type = frappe.db.get_value("Account", account, "account_type") - return { - "balance": get_balance_on(account, date), - "party_type": {"Receivable":"Customer", "Payable":"Supplier"}.get(account_type, "") - } + company_currency = get_company_currency(company) + account_details = frappe.db.get_value("Account", account, ["account_type", "account_currency"], as_dict=1) + if account_details.account_type == "Receivable": + party_type = "Customer" + elif account_details.account_type == "Payable": + party_type = "Supplier" + else: + party_type = "" + + grid_values = { + "balance": get_balance_on(account, date), + "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, + company, debit=debit, credit=credit, exchange_rate=exchange_rate) + } + return grid_values + +@frappe.whitelist() +def get_exchange_rate(account, account_currency, company, + reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None): + from erpnext.setup.utils import get_exchange_rate + company_currency = get_company_currency(company) + account_details = frappe.db.get_value("Account", account, ["account_type", "root_type"], as_dict=1) + + if account_currency != company_currency: + if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name: + exchange_rate = frappe.db.get_value(reference_type, reference_name, "conversion_rate") + elif account_details.account_type == "Bank" and \ + ((account_details.root_type == "Asset" and flt(credit) > 0) or + (account_details.root_type == "Liability" and debit)): + exchange_rate = get_average_exchange_rate(account) + + if not exchange_rate: + exchange_rate = get_exchange_rate(account_currency, company_currency) + else: + exchange_rate = 1 + + return exchange_rate + +def get_average_exchange_rate(account): + exchange_rate = 0 + bank_balance_in_account_currency = get_balance_on(account) + if bank_balance_in_account_currency: + bank_balance_in_company_currency = get_balance_on(account, in_account_currency=False) + exchange_rate = bank_balance_in_company_currency / bank_balance_in_account_currency + + return exchange_rate diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 8995e34220f..753e0126c9b 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -101,7 +101,7 @@ class TestJournalEntry(unittest.TestCase): self.set_total_expense_zero("2013-02-28") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 40000, "_Test Cost Center - _TC", submit=True) + "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", submit=True) self.assertTrue(frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})) @@ -112,7 +112,7 @@ class TestJournalEntry(unittest.TestCase): self.set_total_expense_zero("2013-02-28") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 40000, "_Test Cost Center - _TC") + "_Test Bank - _TC", 40000, "_Test Cost Center - _TC") self.assertRaises(BudgetError, jv.submit) @@ -126,7 +126,7 @@ class TestJournalEntry(unittest.TestCase): self.set_total_expense_zero("2013-02-28") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 150000, "_Test Cost Center - _TC") + "_Test Bank - _TC", 150000, "_Test Cost Center - _TC") self.assertRaises(BudgetError, jv.submit) @@ -136,13 +136,13 @@ class TestJournalEntry(unittest.TestCase): self.set_total_expense_zero("2013-02-28") jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 20000, "_Test Cost Center - _TC", submit=True) + "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True) self.assertTrue(frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv1.name})) jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 20000, "_Test Cost Center - _TC", submit=True) + "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True) self.assertTrue(frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv2.name})) @@ -165,32 +165,80 @@ class TestJournalEntry(unittest.TestCase): def set_total_expense_zero(self, posting_date): existing_expense = self.get_actual_expense(posting_date) make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True) + "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True) + + def test_multi_currency(self): + jv = make_journal_entry("_Test Bank USD - _TC", + "_Test Bank - _TC", 100, exchange_rate=50, save=False) + + jv.get("accounts")[1].credit_in_account_currency = 5000 + jv.submit() + + gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s + order by account asc""", jv.name, as_dict=1) -def make_journal_entry(account1, account2, amount, cost_center=None, submit=False): + self.assertTrue(gl_entries) + + expected_values = { + "_Test Bank USD - _TC": { + "account_currency": "USD", + "debit": 5000, + "debit_in_account_currency": 100, + "credit": 0, + "credit_in_account_currency": 0 + }, + "_Test Bank - _TC": { + "account_currency": "INR", + "debit": 0, + "debit_in_account_currency": 0, + "credit": 5000, + "credit_in_account_currency": 5000 + } + } + + for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"): + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_values[gle.account][field], gle[field]) + + + + # cancel + jv.cancel() + + gle = frappe.db.sql("""select name from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s""", jv.name) + + self.assertFalse(gle) + +def make_journal_entry(account1, account2, amount, cost_center=None, exchange_rate=1, save=True, submit=False): jv = frappe.new_doc("Journal Entry") jv.posting_date = "2013-02-14" jv.company = "_Test Company" jv.fiscal_year = "_Test Fiscal Year 2013" jv.user_remark = "test" - + jv.multi_currency = 1 jv.set("accounts", [ { "account": account1, "cost_center": cost_center, - "debit": amount if amount > 0 else 0, - "credit": abs(amount) if amount < 0 else 0, + "debit_in_account_currency": amount if amount > 0 else 0, + "credit_in_account_currency": abs(amount) if amount < 0 else 0, + "exchange_rate": exchange_rate }, { "account": account2, "cost_center": cost_center, - "credit": amount if amount > 0 else 0, - "debit": abs(amount) if amount < 0 else 0, + "credit_in_account_currency": amount if amount > 0 else 0, + "debit_in_account_currency": abs(amount) if amount < 0 else 0, + exchange_rate: exchange_rate } ]) - jv.insert() + if save or submit: + jv.insert() - if submit: - jv.submit() + if submit: + jv.submit() return jv diff --git a/erpnext/accounts/doctype/journal_entry/test_records.json b/erpnext/accounts/doctype/journal_entry/test_records.json index f6608638e09..5e25c3cb4a5 100644 --- a/erpnext/accounts/doctype/journal_entry/test_records.json +++ b/erpnext/accounts/doctype/journal_entry/test_records.json @@ -9,15 +9,15 @@ "account": "_Test Receivable - _TC", "party_type": "Customer", "party": "_Test Customer", - "credit": 400.0, - "debit": 0.0, + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, "doctype": "Journal Entry Account", "parentfield": "accounts" }, { - "account": "_Test Account Bank Account - _TC", - "credit": 0.0, - "debit": 400.0, + "account": "_Test Bank - _TC", + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, "doctype": "Journal Entry Account", "parentfield": "accounts" } @@ -40,15 +40,15 @@ "account": "_Test Payable - _TC", "party_type": "Supplier", "party": "_Test Supplier", - "credit": 0.0, - "debit": 400.0, + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, "doctype": "Journal Entry Account", "parentfield": "accounts" }, { - "account": "_Test Account Bank Account - _TC", - "credit": 400.0, - "debit": 0.0, + "account": "_Test Bank - _TC", + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, "doctype": "Journal Entry Account", "parentfield": "accounts" } @@ -71,16 +71,16 @@ "account": "_Test Receivable - _TC", "party_type": "Customer", "party": "_Test Customer", - "credit": 0.0, - "debit": 400.0, + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, "doctype": "Journal Entry Account", "parentfield": "accounts" }, { "account": "Sales - _TC", "cost_center": "_Test Cost Center - _TC", - "credit": 400.0, - "debit": 0.0, + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, "doctype": "Journal Entry Account", "parentfield": "accounts" } diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index f63722d13cf..577df2510c8 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -34,6 +34,28 @@ "unique": 0, "width": "250px" }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "account_type", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Account Type", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -48,7 +70,7 @@ "no_copy": 1, "oldfieldname": "balance", "oldfieldtype": "Data", - "options": "Company:company:default_currency", + "options": "account_currency", "permlevel": 0, "print_hide": 1, "read_only": 1, @@ -162,7 +184,7 @@ "in_list_view": 0, "label": "Party Balance", "no_copy": 0, - "options": "Company:company:default_currency", + "options": "account_currency", "permlevel": 0, "precision": "", "print_hide": 0, @@ -173,6 +195,96 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": "", + "depends_on": "", + "fieldname": "currency_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Currency", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "account_currency", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Account Currency", + "no_copy": 1, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "exchange_rate", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Exchange Rate", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -198,20 +310,43 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "debit", + "fieldname": "debit_in_account_currency", "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 1, - "label": "Debit", + "label": "Debit in Account Currency", "no_copy": 0, + "options": "account_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "debit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Debit in Company Currency", + "no_copy": 1, "oldfieldname": "debit", "oldfieldtype": "Currency", "options": "Company:company:default_currency", "permlevel": 0, - "print_hide": 0, - "read_only": 0, + "print_hide": 1, + "read_only": 1, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -242,20 +377,43 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "credit", + "fieldname": "credit_in_account_currency", "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 1, - "label": "Credit", + "label": "Credit in Account Currency", "no_copy": 0, + "options": "account_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "credit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Credit in Company Currency", + "no_copy": 1, "oldfieldname": "credit", "oldfieldtype": "Currency", "options": "Company:company:default_currency", "permlevel": 0, - "print_hide": 0, - "read_only": 0, + "print_hide": 1, + "read_only": 1, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -405,7 +563,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-08-17 02:11:33.991361", + "modified": "2015-09-09 12:55:59.270539", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", diff --git a/erpnext/accounts/doctype/payment_tool/payment_tool.js b/erpnext/accounts/doctype/payment_tool/payment_tool.js index 6eefdb936c3..5904b99d0a4 100644 --- a/erpnext/accounts/doctype/payment_tool/payment_tool.js +++ b/erpnext/accounts/doctype/payment_tool/payment_tool.js @@ -55,6 +55,25 @@ frappe.ui.form.on("Payment Tool", "party", function(frm) { } }) +frappe.ui.form.on("Payment Tool", "party_account", function(frm) { + if(frm.doc.party_account) { + frm.call({ + method: "frappe.client.get_value", + args: { + doctype: "Account", + fieldname: "account_currency", + filters: { name: frm.doc.party_account }, + }, + callback: function(r, rt) { + if(r.message) { + frm.set_value("party_account_currency", r.message.account_currency); + erpnext.payment_tool.check_mandatory_to_set_button(frm); + } + } + }); + } +}) + frappe.ui.form.on("Payment Tool", "company", function(frm) { erpnext.payment_tool.check_mandatory_to_set_button(frm); }); @@ -63,10 +82,6 @@ frappe.ui.form.on("Payment Tool", "received_or_paid", function(frm) { erpnext.payment_tool.check_mandatory_to_set_button(frm); }); -frappe.ui.form.on("Payment Tool", "party", function(frm) { - erpnext.payment_tool.check_mandatory_to_set_button(frm); -}); - // Fetch bank/cash account based on payment mode frappe.ui.form.on("Payment Tool", "payment_mode", function(frm) { return frappe.call({ @@ -158,7 +173,9 @@ frappe.ui.form.on("Payment Tool Detail", "against_voucher_no", function(frm, cdt method: 'erpnext.accounts.doctype.payment_tool.payment_tool.get_against_voucher_amount', args: { "against_voucher_type": row.against_voucher_type, - "against_voucher_no": row.against_voucher_no + "against_voucher_no": row.against_voucher_no, + "party_account": self.party_account, + "company": self.company }, callback: function(r) { if(!r.exc) { @@ -216,4 +233,4 @@ erpnext.payment_tool.check_mandatory_to_fetch = function(doc) { $.each(["Company", "Party Type", "Party", "Received or Paid"], function(i, field) { if(!doc[frappe.model.scrub(field)]) frappe.throw(__("Please select {0} first", [field])); }); -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_tool/payment_tool.json b/erpnext/accounts/doctype/payment_tool/payment_tool.json index e00d9e29abf..55e70481e2c 100644 --- a/erpnext/accounts/doctype/payment_tool/payment_tool.json +++ b/erpnext/accounts/doctype/payment_tool/payment_tool.json @@ -106,7 +106,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Column Break 1", + "label": "", "no_copy": 0, "permlevel": 0, "print_hide": 0, @@ -162,6 +162,29 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Party Account Currency", + "no_copy": 1, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -306,6 +329,7 @@ "in_list_view": 0, "label": "Total Payment Amount", "no_copy": 0, + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "read_only": 1, @@ -450,7 +474,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-06-05 11:17:33.843334", + "modified": "2015-08-31 18:58:21.813054", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Tool", diff --git a/erpnext/accounts/doctype/payment_tool/payment_tool.py b/erpnext/accounts/doctype/payment_tool/payment_tool.py index 4edbebd09f7..924fd1e1c6d 100644 --- a/erpnext/accounts/doctype/payment_tool/payment_tool.py +++ b/erpnext/accounts/doctype/payment_tool/payment_tool.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe import _ +from frappe import _, scrub from frappe.utils import flt from frappe.model.document import Document import json @@ -33,15 +33,18 @@ class PaymentTool(Document): d1.party_type = self.party_type d1.party = self.party d1.balance = get_balance_on(self.party_account) - d1.set("debit" if self.received_or_paid=="Paid" else "credit", flt(v.payment_amount)) + d1.set("debit_in_account_currency" if self.received_or_paid=="Paid" \ + else "credit_in_account_currency", flt(v.payment_amount)) d1.set("reference_type", v.against_voucher_type) d1.set("reference_name", v.against_voucher_no) d1.set('is_advance', 'Yes' if v.against_voucher_type in ['Sales Order', 'Purchase Order'] else 'No') - total_payment_amount = flt(total_payment_amount) + flt(d1.debit) - flt(d1.credit) + total_payment_amount = flt(total_payment_amount) + \ + flt(d1.debit_in_account_currency) - flt(d1.credit_in_account_currency) d2 = jv.append("accounts") d2.account = self.payment_account - d2.set('debit' if total_payment_amount < 0 else 'credit', abs(total_payment_amount)) + d2.set('debit_in_account_currency' if total_payment_amount < 0 \ + else 'credit_in_account_currency', abs(total_payment_amount)) if self.payment_account: d2.balance = get_balance_on(self.payment_account) @@ -55,11 +58,14 @@ def get_outstanding_vouchers(args): frappe.throw(_("No permission to use Payment Tool"), frappe.PermissionError) args = json.loads(args) + + party_account_currency = frappe.db.get_value("Account", args.get("party_account"), "account_currency") + company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency") if args.get("party_type") == "Customer" and args.get("received_or_paid") == "Received": - amount_query = "ifnull(debit, 0) - ifnull(credit, 0)" + amount_query = "ifnull(debit_in_account_currency, 0) - ifnull(credit_in_account_currency, 0)" elif args.get("party_type") == "Supplier" and args.get("received_or_paid") == "Paid": - amount_query = "ifnull(credit, 0) - ifnull(debit, 0)" + amount_query = "ifnull(credit_in_account_currency, 0) - ifnull(debit_in_account_currency, 0)" else: frappe.throw(_("Please enter the Against Vouchers manually")) @@ -68,27 +74,34 @@ def get_outstanding_vouchers(args): args.get("party_type"), args.get("party")) # 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("party_type"), args.get("party"), + party_account_currency, company_currency) return outstanding_invoices + orders_to_be_billed -def get_orders_to_be_billed(party_type, party): +def get_orders_to_be_billed(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" + orders = frappe.db.sql(""" select name as voucher_no, - ifnull(base_grand_total, 0) as invoice_amount, - (ifnull(base_grand_total, 0) - ifnull(advance_paid, 0)) as outstanding_amount, + ifnull({ref_field}, 0) as invoice_amount, + (ifnull({ref_field}, 0) - ifnull(advance_paid, 0)) as outstanding_amount, transaction_date as posting_date from - `tab%s` + `tab{voucher_type}` where - %s = %s + {party_type} = %s and docstatus = 1 and ifnull(status, "") != "Stopped" - and ifnull(base_grand_total, 0) > ifnull(advance_paid, 0) + and ifnull({ref_field}, 0) > ifnull(advance_paid, 0) and abs(100 - ifnull(per_billed, 0)) > 0.01 - """ % (voucher_type, 'customer' if party_type == "Customer" else 'supplier', '%s'), - party, as_dict = True) + """.format(**{ + "ref_field": ref_field, + "voucher_type": voucher_type, + "party_type": scrub(party_type) + }), party, as_dict = True) order_list = [] for d in orders: @@ -98,13 +111,19 @@ def get_orders_to_be_billed(party_type, party): return order_list @frappe.whitelist() -def get_against_voucher_amount(against_voucher_type, against_voucher_no): +def get_against_voucher_amount(against_voucher_type, against_voucher_no, party_account, company): + party_account_currency = frappe.db.get_value("Account", party_account, "account_currency") + company_currency = frappe.db.get_value("Company", company, "default_currency") + ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total" + if against_voucher_type in ["Sales Order", "Purchase Order"]: - select_cond = "base_grand_total as total_amount, ifnull(base_grand_total, 0) - ifnull(advance_paid, 0) as outstanding_amount" + select_cond = "{0} as total_amount, ifnull({0}, 0) - ifnull(advance_paid, 0) as outstanding_amount"\ + .format(ref_field) elif against_voucher_type in ["Sales Invoice", "Purchase Invoice"]: - select_cond = "base_grand_total as total_amount, outstanding_amount" + select_cond = "{0} as total_amount, outstanding_amount".format(ref_field) elif against_voucher_type == "Journal Entry": - select_cond = "total_debit as total_amount" + ref_field = "total_debit" if party_account_currency == company_currency else "total_debit/exchange_rate" + select_cond = "{0} as total_amount".format(ref_field) details = frappe.db.sql("""select {0} from `tab{1}` where name = %s""" .format(select_cond, against_voucher_type), against_voucher_no, as_dict=1) diff --git a/erpnext/accounts/doctype/payment_tool/test_payment_tool.py b/erpnext/accounts/doctype/payment_tool/test_payment_tool.py index 321986cd466..4f1c9e98a4a 100644 --- a/erpnext/accounts/doctype/payment_tool/test_payment_tool.py +++ b/erpnext/accounts/doctype/payment_tool/test_payment_tool.py @@ -39,7 +39,7 @@ class TestPaymentTool(unittest.TestCase): "party": "_Test Customer 3", "reference_type": "Sales Order", "reference_name": so2.name, - "credit": 1000, + "credit_in_account_currency": 1000, "is_advance": "Yes" }) @@ -67,7 +67,7 @@ class TestPaymentTool(unittest.TestCase): "party": "_Test Customer 3", "reference_type": si2.doctype, "reference_name": si2.name, - "credit": 561.80 + "credit_in_account_currency": 561.80 }) pi = self.create_voucher(pi_test_records[0], { @@ -91,7 +91,7 @@ class TestPaymentTool(unittest.TestCase): "party": "_Test Customer 3", "party_account": "_Test Receivable - _TC", "payment_mode": "Cheque", - "payment_account": "_Test Account Bank Account - _TC", + "payment_account": "_Test Bank - _TC", "reference_no": "123456", "reference_date": "2013-02-14" } @@ -117,10 +117,10 @@ class TestPaymentTool(unittest.TestCase): def create_against_jv(self, test_record, args): jv = frappe.copy_doc(test_record) jv.get("accounts")[0].update(args) - if args.get("debit"): - jv.get("accounts")[1].credit = args["debit"] - elif args.get("credit"): - jv.get("accounts")[1].debit = args["credit"] + if args.get("debit_in_account_currency"): + jv.get("accounts")[1].credit_in_account_currency = args["debit_in_account_currency"] + elif args.get("credit_in_account_currency"): + jv.get("accounts")[1].debit_in_account_currency = args["credit_in_account_currency"] jv.insert() jv.submit() @@ -141,7 +141,8 @@ class TestPaymentTool(unittest.TestCase): outstanding_entries = get_outstanding_vouchers(json.dumps(args)) for d in outstanding_entries: - self.assertEquals(flt(d.get("outstanding_amount"), 2), expected_outstanding.get(d.get("voucher_type"))[1]) + self.assertEquals(flt(d.get("outstanding_amount"), 2), + expected_outstanding.get(d.get("voucher_type"))[1]) self.check_jv_entries(doc, outstanding_entries, expected_outstanding) @@ -156,11 +157,10 @@ class TestPaymentTool(unittest.TestCase): paytool.total_payment_amount = 300 new_jv = paytool.make_journal_entry() - for jv_entry in new_jv.get("accounts"): if paytool.party_account == jv_entry.get("account") and paytool.party == jv_entry.get("party"): - self.assertEquals(100.00, - jv_entry.get("debit" if paytool.party_type=="Supplier" else "credit")) + self.assertEquals(100.00, jv_entry.get("debit_in_account_currency" + if paytool.party_type=="Supplier" else "credit_in_account_currency")) self.assertEquals(jv_entry.reference_name, expected_outstanding[jv_entry.reference_type][0]) @@ -170,4 +170,6 @@ class TestPaymentTool(unittest.TestCase): def clear_table_entries(self): frappe.db.sql("""delete from `tabGL Entry` where party in ("_Test Customer 3", "_Test Supplier 1")""") frappe.db.sql("""delete from `tabSales Order` where customer = "_Test Customer 3" """) + frappe.db.sql("""delete from `tabSales Invoice` where customer = "_Test Customer 3" """) frappe.db.sql("""delete from `tabPurchase Order` where supplier = "_Test Supplier 1" """) + frappe.db.sql("""delete from `tabPurchase Invoice` where supplier = "_Test Supplier 1" """) diff --git a/erpnext/accounts/doctype/payment_tool_detail/payment_tool_detail.json b/erpnext/accounts/doctype/payment_tool_detail/payment_tool_detail.json index 5221f35a3fd..7e1460863ad 100644 --- a/erpnext/accounts/doctype/payment_tool_detail/payment_tool_detail.json +++ b/erpnext/accounts/doctype/payment_tool_detail/payment_tool_detail.json @@ -87,6 +87,7 @@ "in_list_view": 1, "label": "Total Amount", "no_copy": 0, + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "read_only": 1, @@ -108,6 +109,7 @@ "in_list_view": 1, "label": "Outstanding Amount", "no_copy": 0, + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "read_only": 1, @@ -129,6 +131,7 @@ "in_list_view": 1, "label": "Payment Amount", "no_copy": 0, + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "read_only": 0, @@ -146,7 +149,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2014-09-11 08:55:34.384017", + "modified": "2015-08-31 18:58:35.537060", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Tool Detail", diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index afff47fd40c..0b597462a9a 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -10,11 +10,11 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ class TestPeriodClosingVoucher(unittest.TestCase): def test_closing_entry(self): - make_journal_entry("_Test Account Bank Account - _TC", "Sales - _TC", 400, + make_journal_entry("_Test Bank - _TC", "Sales - _TC", 400, "_Test Cost Center - _TC", submit=True) make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Account Bank Account - _TC", 600, "_Test Cost Center - _TC", submit=True) + "_Test Bank - _TC", 600, "_Test Cost Center - _TC", submit=True) profit_or_loss = frappe.db.sql("""select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) as balance from `tabGL Entry` t1, `tabAccount` t2 diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 70ebee1ef6e..2e3794a2385 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -75,8 +75,29 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ me.apply_pricing_rule(); }) }, + + credit_to: function() { + var me = this; + if(this.frm.doc.credit_to) { + me.frm.call({ + method: "frappe.client.get_value", + args: { + doctype: "Account", + fieldname: "account_currency", + filters: { name: me.frm.doc.credit_to }, + }, + callback: function(r, rt) { + if(r.message) { + me.frm.set_value("party_account_currency", r.message.account_currency); + me.set_dynamic_labels(); + } + } + }); + } + }, write_off_amount: function() { + this.set_in_company_currency(this.frm.doc, ["write_off_amount"]); this.calculate_outstanding_amount(); this.frm.refresh_fields(); }, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3fb048b5636..6937c97e8c7 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1266,30 +1266,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "total_amount_to_pay", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Total Amount To Pay", - "no_copy": 1, - "oldfieldname": "total_amount_to_pay", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -1304,7 +1280,7 @@ "no_copy": 1, "oldfieldname": "total_advance", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 1, "read_only": 1, @@ -1328,7 +1304,7 @@ "no_copy": 1, "oldfieldname": "outstanding_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 1, "read_only": 1, @@ -1338,6 +1314,30 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "write_off_amount", + "depends_on": "grand_total", + "fieldname": "write_off", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write Off", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1350,7 +1350,7 @@ "in_list_view": 0, "label": "Write Off Amount", "no_copy": 1, - "options": "Company:company:default_currency", + "options": "currency", "permlevel": 0, "print_hide": 1, "read_only": 0, @@ -1360,6 +1360,50 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "base_write_off_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write Off Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_61", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1406,29 +1450,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "against_expense_account", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Against Expense Account", - "no_copy": 1, - "oldfieldname": "against_expense_account", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 1, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -1748,6 +1769,29 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Party Account Currency", + "no_copy": 1, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1797,6 +1841,29 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "against_expense_account", + "fieldtype": "Small Text", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Against Expense Account", + "no_copy": 1, + "oldfieldname": "against_expense_account", + "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a43e553ea00..d92e1fa0ccd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -91,13 +91,16 @@ class PurchaseInvoice(BuyingController): throw(_("Conversion rate cannot be 0 or 1")) def validate_credit_to_acc(self): - account = frappe.db.get_value("Account", self.credit_to, ["account_type", "report_type"], as_dict=True) + account = frappe.db.get_value("Account", self.credit_to, + ["account_type", "report_type", "account_currency"], as_dict=True) if account.report_type != "Balance Sheet": frappe.throw(_("Credit To account must be a Balance Sheet account")) if self.supplier and account.account_type != "Payable": frappe.throw(_("Credit To account must be a Payable account")) + + self.party_account_currency = account.account_currency def check_for_stopped_status(self): check_list = [] @@ -213,7 +216,7 @@ class PurchaseInvoice(BuyingController): 'party_type': 'Supplier', 'party': self.supplier, 'is_advance' : 'Yes', - 'dr_or_cr' : 'debit', + 'dr_or_cr' : 'debit_in_account_currency', 'unadjusted_amt' : flt(d.advance_amount), 'allocated_amt' : flt(d.allocated_amount) } @@ -248,7 +251,7 @@ class PurchaseInvoice(BuyingController): expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") gl_entries = [] - + # parent's gl entry if self.base_grand_total: gl_entries.append( @@ -257,26 +260,32 @@ class PurchaseInvoice(BuyingController): "party_type": "Supplier", "party": self.supplier, "against": self.against_expense_account, - "credit": self.total_amount_to_pay, - "remarks": self.remarks, + "credit": self.base_grand_total, + "credit_in_account_currency": self.base_grand_total \ + if self.party_account_currency==self.company_currency else self.grand_total, "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, - }) + }, self.party_account_currency) ) # tax table gl entries valuation_tax = {} for tax in self.get("taxes"): if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): + account_currency = frappe.db.get_value("Account", tax.account_head, "account_currency") + + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + gl_entries.append( self.get_gl_dict({ "account": tax.account_head, "against": self.supplier, - "debit": tax.add_deduct_tax == "Add" and tax.base_tax_amount_after_discount_amount or 0, - "credit": tax.add_deduct_tax == "Deduct" and tax.base_tax_amount_after_discount_amount or 0, - "remarks": self.remarks, + dr_or_cr: tax.base_tax_amount_after_discount_amount, + dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==self.company_currency \ + else tax.tax_amount_after_discount_amount, "cost_center": tax.cost_center - }) + }, account_currency) ) # accumulate valuation tax @@ -292,14 +301,16 @@ class PurchaseInvoice(BuyingController): stock_items = self.get_stock_items() for item in self.get("items"): if flt(item.base_net_amount): + account_currency = frappe.db.get_value("Account", item.expense_account, "account_currency") gl_entries.append( self.get_gl_dict({ "account": item.expense_account, "against": self.supplier, "debit": item.base_net_amount, - "remarks": self.remarks, + "debit_in_account_currency": item.base_net_amount \ + if account_currency==self.company_currency else item.net_amount, "cost_center": item.cost_center - }) + }, account_currency) ) if auto_accounting_for_stock and self.is_opening == "No" and \ @@ -352,12 +363,28 @@ class PurchaseInvoice(BuyingController): # writeoff account includes petty difference in the invoice amount # and the amount that is paid if self.write_off_account and flt(self.write_off_amount): + write_off_account_currency = frappe.db.get_value("Account", self.write_off_account, "account_currency") + + gl_entries.append( + self.get_gl_dict({ + "account": self.credit_to, + "party_type": "Supplier", + "party": self.supplier, + "against": self.write_off_account, + "debit": self.base_write_off_amount, + "debit_in_account_currency": self.base_write_off_amount \ + if self.party_account_currency==self.company_currency else self.write_off_amount, + "against_voucher": self.return_against if cint(self.is_return) else self.name, + "against_voucher_type": self.doctype, + }, self.party_account_currency) + ) gl_entries.append( self.get_gl_dict({ "account": self.write_off_account, "against": self.supplier, - "credit": flt(self.write_off_amount), - "remarks": self.remarks, + "credit": flt(self.base_write_off_amount), + "credit_in_account_currency": self.base_write_off_amount \ + if write_off_account_currency==self.company_currency else self.write_off_amount, "cost_center": self.write_off_cost_center }) ) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0b74948d0e1..b39f30bbdcc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -10,6 +10,7 @@ from frappe.utils import cint import frappe.defaults from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ test_records as pr_test_records +from erpnext.controllers.accounts_controller import InvalidCurrency test_dependencies = ["Item", "Cost Center"] test_ignore = ["Serial No"] @@ -218,7 +219,8 @@ class TestPurchaseInvoice(unittest.TestCase): pi.load_from_db() self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account` - where reference_type='Purchase Invoice' and reference_name=%s and debit=300""", pi.name)) + where reference_type='Purchase Invoice' + and reference_name=%s and debit_in_account_currency=300""", pi.name)) self.assertEqual(pi.outstanding_amount, 1212.30) @@ -276,6 +278,55 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEquals(expected_values[gle.account][1], gle.credit) set_perpetual_inventory(0) + + def test_multi_currency_gle(self): + set_perpetual_inventory(0) + + pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC", + currency="USD", conversion_rate=50) + + gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", pi.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_values = { + "_Test Payable USD - _TC": { + "account_currency": "USD", + "debit": 0, + "debit_in_account_currency": 0, + "credit": 12500, + "credit_in_account_currency": 250 + }, + "_Test Account Cost for Goods Sold - _TC": { + "account_currency": "INR", + "debit": 12500, + "debit_in_account_currency": 12500, + "credit": 0, + "credit_in_account_currency": 0 + } + } + + for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"): + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_values[gle.account][field], gle[field]) + + + # Check for valid currency + pi1 = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC", + do_not_save=True) + + self.assertRaises(InvalidCurrency, pi1.save) + + # cancel + pi.cancel() + + gle = frappe.db.sql("""select name from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s""", pi.name) + + self.assertFalse(gle) def make_purchase_invoice(**args): pi = frappe.new_doc("Purchase Invoice") diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index 679e87024ac..e6961d978bc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -141,6 +141,9 @@ "supplier": "_Test Supplier", "supplier_name": "_Test Supplier" }, + + + { "bill_no": "NA", "buying_price_list": "_Test Price List", diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index c9139d1be1c..f190809a15a 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -117,7 +117,7 @@ "no_copy": 1, "oldfieldname": "advance_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "print_width": "100px", @@ -143,7 +143,7 @@ "no_copy": 1, "oldfieldname": "allocated_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "print_width": "100px", @@ -164,7 +164,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2014-12-25 16:29:15.176476", + "modified": "2015-08-25 17:51:30.274069", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Advance", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index ec84302f1ee..5e8d3a5d9b7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -175,6 +175,26 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte me.apply_pricing_rule(); }) }, + + debit_to: function() { + var me = this; + if(this.frm.doc.debit_to) { + me.frm.call({ + method: "frappe.client.get_value", + args: { + doctype: "Account", + fieldname: "account_currency", + filters: { name: me.frm.doc.debit_to }, + }, + callback: function(r, rt) { + if(r.message) { + me.frm.set_value("party_account_currency", r.message.account_currency); + me.set_dynamic_labels(); + } + } + }); + } + }, allocated_amount: function() { this.calculate_total_advance(); @@ -183,10 +203,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte write_off_outstanding_amount_automatically: function() { if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) { - frappe.model.round_floats_in(this.frm.doc, ["base_grand_total", "paid_amount"]); + frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]); // this will make outstanding amount 0 this.frm.set_value("write_off_amount", - flt(this.frm.doc.base_grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount")) + flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount")) ); this.frm.toggle_enable("write_off_amount", false); @@ -199,10 +219,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, write_off_amount: function() { + this.set_in_company_currency(this.frm.doc, ["write_off_amount"]); this.write_off_outstanding_amount_automatically(); }, paid_amount: function() { + this.set_in_company_currency(this.frm.doc, ["paid_amount"]); this.write_off_outstanding_amount_automatically(); }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 3262527d115..263bbe2ebe2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1145,7 +1145,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Discount", + "label": "Additional Discount", "no_copy": 0, "permlevel": 0, "precision": "", @@ -1448,7 +1448,7 @@ "no_copy": 0, "oldfieldname": "total_advance", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 1, "read_only": 1, @@ -1472,7 +1472,7 @@ "no_copy": 1, "oldfieldname": "outstanding_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 1, "read_only": 1, @@ -1663,7 +1663,7 @@ "no_copy": 1, "oldfieldname": "paid_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "currency", "permlevel": 0, "print_hide": 1, "read_only": 0, @@ -1673,6 +1673,29 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Paid Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1710,7 +1733,7 @@ "in_list_view": 0, "label": "Write Off Amount", "no_copy": 1, - "options": "Company:company:default_currency", + "options": "currency", "permlevel": 0, "print_hide": 1, "read_only": 0, @@ -1720,6 +1743,29 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "base_write_off_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write Off Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -2227,6 +2273,29 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Party Account Currency", + "no_copy": 1, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 2ebe25da0c0..030fddbb997 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -271,7 +271,7 @@ class SalesInvoice(SellingController): 'party_type': 'Customer', 'party': self.customer, 'is_advance' : 'Yes', - 'dr_or_cr' : 'credit', + 'dr_or_cr' : 'credit_in_account_currency', 'unadjusted_amt' : flt(d.advance_amount), 'allocated_amt' : flt(d.allocated_amount) } @@ -282,13 +282,16 @@ class SalesInvoice(SellingController): reconcile_against_document(lst) def validate_debit_to_acc(self): - account = frappe.db.get_value("Account", self.debit_to, ["account_type", "report_type"], as_dict=True) + account = frappe.db.get_value("Account", self.debit_to, + ["account_type", "report_type", "account_currency"], as_dict=True) if account.report_type != "Balance Sheet": frappe.throw(_("Debit To account must be a Balance Sheet account")) if self.customer and account.account_type != "Receivable": frappe.throw(_("Debit To account must be a Receivable account")) + + self.party_account_currency = account.account_currency def validate_fixed_asset_account(self): """Validate Fixed Asset and whether Income Account Entered Exists""" @@ -434,15 +437,18 @@ class SalesInvoice(SellingController): if cint(self.is_pos) == 1: if flt(self.paid_amount) == 0: if self.cash_bank_account: - frappe.db.set(self, 'paid_amount', - (flt(self.base_grand_total) - flt(self.write_off_amount))) + frappe.db.set(self, 'paid_amount', + flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount"))) else: # show message that the amount is not paid frappe.db.set(self,'paid_amount',0) frappe.msgprint(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) else: frappe.db.set(self,'paid_amount',0) - + + frappe.db.set(self, 'base_paid_amount', + flt(self.paid_amount*self.conversion_rate, self.precision("base_paid_amount"))) + def check_prev_docstatus(self): for d in self.get('items'): if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1: @@ -481,7 +487,7 @@ class SalesInvoice(SellingController): from erpnext.accounts.general_ledger import merge_similar_entries gl_entries = [] - + self.make_customer_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) @@ -498,7 +504,7 @@ class SalesInvoice(SellingController): return gl_entries def make_customer_gl_entry(self, gl_entries): - if self.base_grand_total: + if self.grand_total: gl_entries.append( self.get_gl_dict({ "account": self.debit_to, @@ -506,37 +512,42 @@ class SalesInvoice(SellingController): "party": self.customer, "against": self.against_income_account, "debit": self.base_grand_total, - "remarks": self.remarks, + "debit_in_account_currency": self.base_grand_total \ + if self.party_account_currency==self.company_currency else self.grand_total, "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype - }) + }, self.party_account_currency) ) def make_tax_gl_entries(self, gl_entries): for tax in self.get("taxes"): if flt(tax.base_tax_amount_after_discount_amount): + account_currency = frappe.db.get_value("Account", tax.account_head, "account_currency") gl_entries.append( self.get_gl_dict({ "account": tax.account_head, "against": self.customer, "credit": flt(tax.base_tax_amount_after_discount_amount), - "remarks": self.remarks, + "credit_in_account_currency": flt(tax.base_tax_amount_after_discount_amount) \ + if account_currency==self.company_currency else flt(tax.tax_amount_after_discount_amount), "cost_center": tax.cost_center - }) + }, account_currency) ) def make_item_gl_entries(self, gl_entries): # income account gl entries for item in self.get("items"): if flt(item.base_net_amount): + account_currency = frappe.db.get_value("Account", item.income_account, "account_currency") gl_entries.append( self.get_gl_dict({ "account": item.income_account, "against": self.customer, "credit": item.base_net_amount, - "remarks": self.remarks, + "credit_in_account_currency": item.base_net_amount \ + if account_currency==self.company_currency else item.net_amount, "cost_center": item.cost_center - }) + }, account_currency) ) # expense account gl entries @@ -546,6 +557,7 @@ class SalesInvoice(SellingController): def make_pos_gl_entries(self, gl_entries): if cint(self.is_pos) and self.cash_bank_account and self.paid_amount: + bank_account_currency = frappe.db.get_value("Account", self.cash_bank_account, "account_currency") # POS, make payment entries gl_entries.append( self.get_gl_dict({ @@ -553,44 +565,50 @@ class SalesInvoice(SellingController): "party_type": "Customer", "party": self.customer, "against": self.cash_bank_account, - "credit": self.paid_amount, - "remarks": self.remarks, + "credit": self.base_paid_amount, + "credit_in_account_currency": self.base_paid_amount \ + if self.party_account_currency==self.company_currency else self.paid_amount, "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, - }) + }, self.party_account_currency) ) gl_entries.append( self.get_gl_dict({ "account": self.cash_bank_account, "against": self.customer, - "debit": self.paid_amount, - "remarks": self.remarks, - }) + "debit": self.base_paid_amount, + "debit_in_account_currency": self.base_paid_amount \ + if bank_account_currency==self.company_currency else self.paid_amount + }, bank_account_currency) ) def make_write_off_gl_entry(self, gl_entries): # write off entries, applicable if only pos if self.write_off_account and self.write_off_amount: + write_off_account_currency = frappe.db.get_value("Account", self.write_off_account, "account_currency") + gl_entries.append( self.get_gl_dict({ "account": self.debit_to, "party_type": "Customer", "party": self.customer, "against": self.write_off_account, - "credit": self.write_off_amount, - "remarks": self.remarks, + "credit": self.base_write_off_amount, + "credit_in_account_currency": self.base_write_off_amount \ + if self.party_account_currency==self.company_currency else self.write_off_amount, "against_voucher": self.return_against if cint(self.is_return) else self.name, - "against_voucher_type": self.doctype, - }) + "against_voucher_type": self.doctype + }, self.party_account_currency) ) gl_entries.append( self.get_gl_dict({ "account": self.write_off_account, "against": self.customer, - "debit": self.write_off_amount, - "remarks": self.remarks, + "debit": self.base_write_off_amount, + "debit_in_account_currency": self.base_write_off_amount \ + if write_off_account_currency==self.company_currency else self.write_off_amount, "cost_center": self.write_off_cost_center - }) + }, write_off_account_currency) ) def get_list_context(context=None): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c7a992c489c..449f98d8f2d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -7,6 +7,8 @@ import unittest, copy from frappe.utils import nowdate, add_days, flt from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory +from erpnext.controllers.accounts_controller import InvalidCurrency +from erpnext.accounts.doctype.gl_entry.gl_entry import InvalidAccountCurrency class TestSalesInvoice(unittest.TestCase): def make(self): @@ -401,7 +403,7 @@ class TestSalesInvoice(unittest.TestCase): jv.cancel() self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 561.8) - def test_sales_invoice_gl_entry_without_aii(self): + def test_sales_invoice_gl_entry_without_perpetual_inventory(self): set_perpetual_inventory(0) si = frappe.copy_doc(test_records[1]) si.insert() @@ -433,7 +435,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) - def test_pos_gl_entry_with_aii(self): + def test_pos_gl_entry_with_perpetual_inventory(self): set_perpetual_inventory() self.make_pos_profile() @@ -442,8 +444,7 @@ class TestSalesInvoice(unittest.TestCase): pos = copy.deepcopy(test_records[1]) pos["is_pos"] = 1 pos["update_stock"] = 1 - # pos["posting_time"] = "12:05" - pos["cash_bank_account"] = "_Test Account Bank Account - _TC" + pos["cash_bank_account"] = "_Test Bank - _TC" pos["paid_amount"] = 600.0 si = frappe.copy_doc(pos) @@ -474,7 +475,7 @@ class TestSalesInvoice(unittest.TestCase): [stock_in_hand, 0.0, abs(sle.stock_value_difference)], [pos["items"][0]["expense_account"], abs(sle.stock_value_difference), 0.0], [si.debit_to, 0.0, 600.0], - ["_Test Account Bank Account - _TC", 600.0, 0.0] + ["_Test Bank - _TC", 600.0, 0.0] ]) for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)): @@ -494,7 +495,7 @@ class TestSalesInvoice(unittest.TestCase): def make_pos_profile(self): pos_profile = frappe.get_doc({ - "cash_bank_account": "_Test Account Bank Account - _TC", + "cash_bank_account": "_Test Bank - _TC", "company": "_Test Company", "cost_center": "_Test Cost Center - _TC", "currency": "INR", @@ -513,7 +514,7 @@ class TestSalesInvoice(unittest.TestCase): if not frappe.db.exists("POS Profile", "_Test POS Profile"): pos_profile.insert() - def test_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self): + def test_si_gl_entry_with_perpetual_inventory_and_update_stock_with_warehouse_but_no_account(self): set_perpetual_inventory() frappe.delete_doc("Account", "_Test Warehouse No Account - _TC") @@ -567,7 +568,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) set_perpetual_inventory(0) - def test_sales_invoice_gl_entry_with_aii_no_item_code(self): + def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self): set_perpetual_inventory() si = frappe.get_doc(test_records[1]) @@ -593,7 +594,7 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory(0) - def test_sales_invoice_gl_entry_with_aii_non_stock_item(self): + def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self): set_perpetual_inventory() si = frappe.get_doc(test_records[1]) si.get("items")[0].item_code = "_Test Non Stock Item" @@ -660,7 +661,7 @@ class TestSalesInvoice(unittest.TestCase): where reference_name=%s""", si.name)) self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account` - where reference_name=%s and credit=300""", si.name)) + where reference_name=%s and credit_in_account_currency=300""", si.name)) self.assertEqual(si.outstanding_amount, 261.8) @@ -841,7 +842,80 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(si.total_taxes_and_charges, 234.44) self.assertEquals(si.base_grand_total, 859.44) self.assertEquals(si.grand_total, 859.44) + + def test_multi_currency_gle(self): + set_perpetual_inventory(0) + si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", + currency="USD", conversion_rate=50) + gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s + order by account asc""", si.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_values = { + "_Test Receivable USD - _TC": { + "account_currency": "USD", + "debit": 5000, + "debit_in_account_currency": 100, + "credit": 0, + "credit_in_account_currency": 0 + }, + "Sales - _TC": { + "account_currency": "INR", + "debit": 0, + "debit_in_account_currency": 0, + "credit": 5000, + "credit_in_account_currency": 5000 + } + } + + for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"): + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_values[gle.account][field], gle[field]) + + # cancel + si.cancel() + + gle = frappe.db.sql("""select name from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) + + self.assertFalse(gle) + + def test_invalid_currency(self): + # Customer currency = USD + + # Transaction currency cannot be INR + si1 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", + do_not_save=True) + + self.assertRaises(InvalidCurrency, si1.save) + + # Transaction currency cannot be EUR + si2 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", + currency="EUR", conversion_rate=80, do_not_save=True) + + self.assertRaises(InvalidCurrency, si2.save) + + # Transaction currency only allowed in USD + si3 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", + currency="USD", conversion_rate=50) + + # Party Account currency must be in USD, as there is existing GLE with USD + si4 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC", + currency="USD", conversion_rate=50, do_not_submit=True) + + self.assertRaises(InvalidAccountCurrency, si4.submit) + + # Party Account currency must be in USD, force customer currency as there is no GLE + + si3.cancel() + si5 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC", + currency="USD", conversion_rate=50, do_not_submit=True) + + self.assertRaises(InvalidAccountCurrency, si5.submit) def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") @@ -856,14 +930,15 @@ def create_sales_invoice(**args): si.is_pos = args.is_pos si.is_return = args.is_return si.return_against = args.return_against - si.currency="INR" - si.conversion_rate = 1 + si.currency=args.currency or "INR" + si.conversion_rate = args.conversion_rate or 1 si.append("items", { "item_code": args.item or args.item_code or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": args.qty or 1, "rate": args.rate or 100, + "income_account": "Sales - _TC", "expense_account": "Cost of Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", "serial_no": args.serial_no diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index e4c039c6359..363e3237a79 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -117,7 +117,7 @@ "no_copy": 1, "oldfieldname": "advance_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "print_width": "120px", @@ -143,7 +143,7 @@ "no_copy": 1, "oldfieldname": "allocated_amount", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "party_account_currency", "permlevel": 0, "print_hide": 0, "print_width": "120px", @@ -164,7 +164,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2014-12-25 16:30:19.446500", + "modified": "2015-08-21 16:22:28.866049", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Advance", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 7edd69f70a1..ed2a0494b2b 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -27,14 +27,25 @@ def process_gl_map(gl_map, merge_entries=True): gl_map = merge_similar_entries(gl_map) for entry in gl_map: - # toggle debit, credit if negative entry + # toggle debit, credit if negative entry if flt(entry.debit) < 0: entry.credit = flt(entry.credit) - flt(entry.debit) entry.debit = 0.0 + + if flt(entry.debit_in_account_currency) < 0: + entry.credit_in_account_currency = \ + flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency) + entry.debit_in_account_currency = 0.0 + if flt(entry.credit) < 0: entry.debit = flt(entry.debit) - flt(entry.credit) entry.credit = 0.0 - + + if flt(entry.credit_in_account_currency) < 0: + entry.debit_in_account_currency = \ + flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency) + entry.credit_in_account_currency = 0.0 + return gl_map def merge_similar_entries(gl_map): @@ -45,7 +56,11 @@ def merge_similar_entries(gl_map): same_head = check_if_in_list(entry, merged_gl_map) if same_head: same_head.debit = flt(same_head.debit) + flt(entry.debit) + same_head.debit_in_account_currency = \ + flt(same_head.debit_in_account_currency) + flt(entry.debit_in_account_currency) same_head.credit = flt(same_head.credit) + flt(entry.credit) + same_head.credit_in_account_currency = \ + flt(same_head.credit_in_account_currency) + flt(entry.credit_in_account_currency) else: merged_gl_map.append(entry) diff --git a/erpnext/accounts/page/accounts_browser/accounts_browser.js b/erpnext/accounts/page/accounts_browser/accounts_browser.js index 157a924e12f..04e4f6354b7 100644 --- a/erpnext/accounts/page/accounts_browser/accounts_browser.js +++ b/erpnext/accounts/page/accounts_browser/accounts_browser.js @@ -214,7 +214,8 @@ erpnext.AccountsChart = Class.extend({ 'Income Account', 'Tax', 'Chargeable', 'Temporary'].join('\n'), description: __("Optional. This setting will be used to filter in various transactions.") }, {fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate')}, - {fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse"} + {fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse"}, + {fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency"} ] }) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 250286c4367..4f49bc07fd6 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -7,10 +7,13 @@ import frappe import datetime from frappe import _, msgprint, scrub from frappe.defaults import get_user_permissions -from frappe.utils import add_days, getdate, formatdate, flt, get_first_day, date_diff, nowdate +from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff from erpnext.utilities.doctype.address.address import get_address_display from erpnext.utilities.doctype.contact.contact import get_contact_details +class InvalidCurrency(frappe.ValidationError): pass +class InvalidAccountCurrency(frappe.ValidationError): pass + @frappe.whitelist() def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None, price_list=None, currency=None, doctype=None): @@ -141,7 +144,61 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, "due_date": get_due_date(posting_date, party_type, party, company) } return out - + +def validate_accounting_currency(party): + company_currency = get_company_currency() + + # set party account currency + if not party.party_account_currency: + if party.default_currency: + party.party_account_currency = party.default_currency + elif len(set(company_currency.values())) == 1: + party.party_account_currency = company_currency.values()[0] + + party_account_currency_in_db = frappe.db.get_value(party.doctype, party.name, "party_account_currency") + if party_account_currency_in_db != party.party_account_currency: + existing_gle = frappe.db.get_value("GL Entry", {"party_type": party.doctype, + "party": party.name}, ["name", "account_currency"], as_dict=1) + if existing_gle: + if party_account_currency_in_db: + frappe.throw(_("Accounting Currency cannot be changed, as GL Entry exists for this {0}") + .format(party.doctype), InvalidCurrency) + else: + party.party_account_currency = existing_gle.account_currency + + +def validate_party_account(party): + company_currency = get_company_currency() + if party.party_account_currency: + companies_with_different_currency = [] + for company, currency in company_currency.items(): + if currency != party.party_account_currency: + companies_with_different_currency.append(company) + + for d in party.get("accounts"): + if d.company in companies_with_different_currency: + companies_with_different_currency.remove(d.company) + + selected_account_currency = frappe.db.get_value("Account", d.account, "account_currency") + if selected_account_currency != party.party_account_currency: + frappe.throw(_("Account {0} is invalid, account currency must be {1}") + .format(d.account, selected_account_currency), InvalidAccountCurrency) + + if companies_with_different_currency: + frappe.msgprint(_("Please mention Default {0} Account for the following companies, as accounting currency is different from company's default currency: {1}") + .format( + "Receivable" if party.doctype=="Customer" else "Payable", + "\n" + "\n".join(companies_with_different_currency) + ) + ) + +def get_company_currency(): + company_currency = frappe._dict() + for d in frappe.get_all("Company", fields=["name", "default_currency"]): + company_currency.setdefault(d.name, d.default_currency) + + return company_currency + @frappe.whitelist() def get_party_account(company, party, party_type): """Returns the account for the given `party`. diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 19703be8ebc..20ecbe92afd 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -30,19 +30,40 @@ class ReceivablePayableReport(object): if args.get("party_type") == "Supplier": columns += [_("Bill No") + "::80", _("Bill Date") + ":Date:80"] - columns += [_("Invoiced Amount") + ":Currency:100", _("Paid Amount") + ":Currency:100", - _("Outstanding Amount") + ":Currency:100", _("Age") + ":Int:50", - "0-" + str(self.filters.range1) + ":Currency:100", - str(self.filters.range1) + "-" + str(self.filters.range2) + ":Currency:100", - str(self.filters.range2) + "-" + str(self.filters.range3) + ":Currency:100", - str(self.filters.range3) + _("-Above") + ":Currency:100" - ] + for label in ("Invoiced Amount", "Paid Amount", "Outstanding Amount"): + columns.append({ + "label": label, + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }) + + columns += [_("Age (Days)") + "::80"] + + for label in ("0-{range1}".format(**self.filters), + "{range1}-{range2}".format(**self.filters), + "{range2}-{range3}".format(**self.filters), + "{range3}-{above}".format(range3=self.filters.range3, above=_("Above"))): + columns.append({ + "label": label, + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }) if args.get("party_type") == "Customer": columns += [_("Territory") + ":Link/Territory:80"] if args.get("party_type") == "Supplier": columns += [_("Supplier Type") + ":Link/Supplier Type:80"] - columns += [_("Remarks") + "::200"] + columns += [ + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Data", + "width": 100, + }, + _("Remarks") + "::200" + ] return columns @@ -91,10 +112,11 @@ class ReceivablePayableReport(object): # customer territory / supplier type if args.get("party_type") == "Customer": - row += [self.get_territory(gle.party), gle.remarks] + row += [self.get_territory(gle.party)] if args.get("party_type") == "Supplier": - row += [self.get_supplier_type(gle.party), gle.remarks] + row += [self.get_supplier_type(gle.party)] + row += [gle.account_currency, gle.remarks] data.append(row) return data @@ -155,7 +177,7 @@ class ReceivablePayableReport(object): def get_voucher_details(self, party_type): voucher_details = frappe._dict() - + if party_type == "Customer": for si in frappe.db.sql("""select name, due_date from `tabSales Invoice` where docstatus=1""", as_dict=1): @@ -171,17 +193,24 @@ class ReceivablePayableReport(object): def get_gl_entries(self, party_type): if not hasattr(self, "gl_entries"): conditions, values = self.prepare_conditions(party_type) - self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party, debit, credit, - voucher_type, voucher_no, against_voucher_type, against_voucher from `tabGL Entry` - where docstatus < 2 and party_type=%s {0} order by posting_date, party""" - .format(conditions), values, as_dict=True) + + if self.filters.get(scrub(party_type)): + select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit" + else: + select_fields = "debit, credit" + + self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party, + voucher_type, voucher_no, against_voucher_type, against_voucher, account_currency, remarks, {0} + from `tabGL Entry` + where docstatus < 2 and party_type=%s {1} order by posting_date, party""" + .format(select_fields, conditions), values, as_dict=True) return self.gl_entries def prepare_conditions(self, party_type): conditions = [""] values = [party_type] - + party_type_field = scrub(party_type) if self.filters.company: @@ -190,7 +219,7 @@ class ReceivablePayableReport(object): if self.filters.get(party_type_field): conditions.append("party=%s") - values.append(self.filters.get(party_type_field)) + values.append(self.filters.get(party_type_field)) return " and ".join(conditions), values diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index e592f69700d..7016c4690b3 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -23,7 +23,8 @@ def execute(filters=None): total_debit += flt(d[2]) total_credit += flt(d[3]) - amounts_not_reflected_in_system = frappe.db.sql("""select sum(ifnull(jvd.debit, 0) - ifnull(jvd.credit, 0)) + amounts_not_reflected_in_system = frappe.db.sql(""" + select sum(ifnull(jvd.debit_in_account_currency, 0) - ifnull(jvd.credit_in_account_currency, 0)) from `tabJournal Entry Account` jvd, `tabJournal Entry` jv where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%s and jv.posting_date > %s and jv.clearance_date <= %s and ifnull(jv.is_opening, 'No') = 'No' @@ -38,7 +39,7 @@ def execute(filters=None): data += [ get_balance_row(_("System Balance"), balance_as_per_system), [""]*len(columns), - ["", '"' + _("Amounts not reflected in bank") + '"', total_debit, total_credit, "", "", "", ""], + ["", '"' + _("Amounts not reflected in bank") + '"', total_debit, total_credit, "", "", "", "", ""], get_balance_row(_("Amounts not reflected in system"), amounts_not_reflected_in_system), [""]*len(columns), get_balance_row(_("Expected balance as per bank"), bank_bal) @@ -49,13 +50,14 @@ def execute(filters=None): def get_columns(): return [_("Posting Date") + ":Date:100", _("Journal Entry") + ":Link/Journal Entry:220", _("Debit") + ":Currency:120", _("Credit") + ":Currency:120", - _("Against Account") + ":Link/Account:200", _("Reference") + "::100", _("Ref Date") + ":Date:110", _("Clearance Date") + ":Date:110" + _("Against Account") + ":Link/Account:200", _("Reference") + "::100", + _("Ref Date") + ":Date:110", _("Clearance Date") + ":Date:110", _("Currency") + ":Link/Currency:70" ] def get_entries(filters): entries = frappe.db.sql("""select - jv.posting_date, jv.name, jvd.debit, jvd.credit, - jvd.against_account, jv.cheque_no, jv.cheque_date, jv.clearance_date + jv.posting_date, jv.name, jvd.debit_in_account_currency, jvd.credit_in_account_currency, + jvd.against_account, jv.cheque_no, jv.cheque_date, jv.clearance_date, jvd.account_currency from `tabJournal Entry Account` jvd, `tabJournal Entry` jv where jvd.parent = jv.name and jv.docstatus=1 @@ -68,6 +70,6 @@ def get_entries(filters): def get_balance_row(label, amount): if amount > 0: - return ["", '"' + label + '"', amount, 0, "", "", "", ""] + return ["", '"' + label + '"', amount, 0, "", "", "", "", ""] else: - return ["", '"' + label + '"', 0, abs(amount), "", "", "", ""] + return ["", '"' + label + '"', 0, abs(amount), "", "", "", "", ""] diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 1cc9f4fc35e..fd91929a76a 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -12,9 +12,12 @@ def execute(filters=None): account_details.setdefault(acc.name, acc) validate_filters(filters, account_details) + validate_party(filters) + + filters = set_account_currency(filters) - columns = get_columns() + columns = get_columns(filters) res = get_result(filters, account_details) @@ -43,36 +46,77 @@ def validate_party(filters): frappe.throw(_("To filter based on Party, select Party Type first")) elif not frappe.db.exists(party_type, party): frappe.throw(_("Invalid {0}: {1}").format(party_type, party)) - -def get_columns(): - return [_("Posting Date") + ":Date:90", _("Account") + ":Link/Account:200", - _("Debit") + ":Float:100", _("Credit") + ":Float:100", + +def set_account_currency(filters): + if not (filters.get("account") or filters.get("party")): + return filters + else: + filters["company_currency"] = frappe.db.get_value("Company", filters.company, "default_currency") + account_currency = None + + if filters.get("account"): + account_currency = frappe.db.get_value("Account", filters.account, "account_currency") + elif filters.get("party"): + gle_currency = frappe.db.get_value("GL Entry", {"party_type": filters.party_type, + "party": filters.party, "company": filters.company}, "account_currency") + if gle_currency: + account_currency = gle_currency + else: + account_currency = frappe.db.get_value(filters.party_type, filters.party, "default_currency") + + filters["account_currency"] = account_currency or filters.company_currency + + if filters.account_currency != filters.company_currency: + filters["show_in_account_currency"] = 1 + + return filters + +def get_columns(filters): + columns = [ + _("Posting Date") + ":Date:90", _("Account") + ":Link/Account:200", + _("Debit") + ":Float:100", _("Credit") + ":Float:100" + ] + + if filters.get("show_in_account_currency"): + columns += [ + _("Debit") + " (" + filters.account_currency + ")" + ":Float:100", + _("Credit") + " (" + filters.account_currency + ")" + ":Float:100" + ] + + columns += [ _("Voucher Type") + "::120", _("Voucher No") + ":Dynamic Link/Voucher Type:160", _("Against Account") + "::120", _("Party Type") + "::80", _("Party") + "::150", - _("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400"] + _("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400" + ] + + return columns def get_result(filters, account_details): gl_entries = get_gl_entries(filters) data = get_data_with_opening_closing(filters, account_details, gl_entries) - result = get_result_as_list(data) + result = get_result_as_list(data, filters) return result def get_gl_entries(filters): + select_fields = """, sum(ifnull(debit_in_account_currency, 0)) as debit_in_account_currency, + sum(ifnull(credit_in_account_currency, 0)) as credit_in_account_currency""" \ + if filters.get("show_in_account_currency") else "" + group_by_condition = "group by voucher_type, voucher_no, account, cost_center" \ if filters.get("group_by_voucher") else "group by name" gl_entries = frappe.db.sql("""select posting_date, account, party_type, party, - sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit, - voucher_type, voucher_no, cost_center, remarks, against, is_opening + sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit, + voucher_type, voucher_no, cost_center, remarks, against, is_opening {select_fields} from `tabGL Entry` where company=%(company)s {conditions} {group_by_condition} order by posting_date, account"""\ - .format(conditions=get_conditions(filters), group_by_condition=group_by_condition), - filters, as_dict=1) + .format(select_fields=select_fields, conditions=get_conditions(filters), + group_by_condition=group_by_condition), filters, as_dict=1) return gl_entries @@ -105,35 +149,51 @@ def get_data_with_opening_closing(filters, account_details, gl_entries): data = [] gle_map = initialize_gle_map(gl_entries) - opening, total_debit, total_credit, gle_map = get_accountwise_gle(filters, gl_entries, gle_map) + opening, total_debit, total_credit, opening_in_account_currency, total_debit_in_account_currency, \ + total_credit_in_account_currency, gle_map = get_accountwise_gle(filters, gl_entries, gle_map) # Opening for filtered account if filters.get("account") or filters.get("party"): - data += [get_balance_row(_("Opening"), opening), {}] + data += [get_balance_row(_("Opening"), opening, opening_in_account_currency), {}] for acc, acc_dict in gle_map.items(): if acc_dict.entries: # Opening for individual ledger, if grouped by account if filters.get("group_by_account"): - data.append(get_balance_row(_("Opening"), acc_dict.opening)) + data.append(get_balance_row(_("Opening"), acc_dict.opening, + acc_dict.opening_in_account_currency)) data += acc_dict.entries # Totals and closing for individual ledger, if grouped by account if filters.get("group_by_account"): + account_closing = acc_dict.opening + acc_dict.total_debit - acc_dict.total_credit + account_closing_in_account_currency = acc_dict.opening_in_account_currency \ + + acc_dict.total_debit_in_account_currency - acc_dict.total_credit_in_account_currency + data += [{"account": "'" + _("Totals") + "'", "debit": acc_dict.total_debit, "credit": acc_dict.total_credit}, get_balance_row(_("Closing (Opening + Totals)"), - (acc_dict.opening + acc_dict.total_debit - acc_dict.total_credit)), {}] + account_closing, account_closing_in_account_currency), {}] # Total debit and credit between from and to date if total_debit or total_credit: - data.append({"account": "'" + _("Totals") + "'", "debit": total_debit, "credit": total_credit}) + data.append({ + "account": "'" + _("Totals") + "'", + "debit": total_debit, + "credit": total_credit, + "debit_in_account_currency": total_debit_in_account_currency, + "credit_in_account_currency": total_credit_in_account_currency + }) # Closing for filtered account if filters.get("account") or filters.get("party"): + closing = opening + total_debit - total_credit + closing_in_account_currency = opening_in_account_currency + \ + total_debit_in_account_currency - total_credit_in_account_currency + data.append(get_balance_row(_("Closing (Opening + Totals)"), - (opening + total_debit - total_credit))) + closing, closing_in_account_currency)) return data @@ -142,46 +202,83 @@ def initialize_gle_map(gl_entries): for gle in gl_entries: gle_map.setdefault(gle.account, frappe._dict({ "opening": 0, + "opening_in_account_currency": 0, "entries": [], "total_debit": 0, + "total_debit_in_account_currency": 0, "total_credit": 0, - "closing": 0 + "total_credit_in_account_currency": 0, + "closing": 0, + "closing_in_account_currency": 0 })) return gle_map def get_accountwise_gle(filters, gl_entries, gle_map): opening, total_debit, total_credit = 0, 0, 0 + opening_in_account_currency, total_debit_in_account_currency, total_credit_in_account_currency = 0, 0, 0 + from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) for gle in gl_entries: amount = flt(gle.debit, 3) - flt(gle.credit, 3) + amount_in_account_currency = flt(gle.debit_in_account_currency, 3) - flt(gle.credit_in_account_currency, 3) + if (filters.get("account") or filters.get("party") or filters.get("group_by_account")) \ and (gle.posting_date < from_date or cstr(gle.is_opening) == "Yes"): + gle_map[gle.account].opening += amount + if filters.get("show_in_account_currency"): + gle_map[gle.account].opening_in_account_currency += amount_in_account_currency + if filters.get("account") or filters.get("party"): opening += amount + if filters.get("show_in_account_currency"): + opening_in_account_currency += amount_in_account_currency + elif gle.posting_date <= to_date: gle_map[gle.account].entries.append(gle) gle_map[gle.account].total_debit += flt(gle.debit, 3) gle_map[gle.account].total_credit += flt(gle.credit, 3) - + total_debit += flt(gle.debit, 3) total_credit += flt(gle.credit, 3) + + if filters.get("show_in_account_currency"): + gle_map[gle.account].total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3) + gle_map[gle.account].total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3) + + total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3) + total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3) - return opening, total_debit, total_credit, gle_map + return opening, total_debit, total_credit, opening_in_account_currency, \ + total_debit_in_account_currency, total_credit_in_account_currency, gle_map -def get_balance_row(label, balance): - return { +def get_balance_row(label, balance, balance_in_account_currency=None): + balance_row = { "account": "'" + label + "'", "debit": balance if balance > 0 else 0, - "credit": -1*balance if balance < 0 else 0, + "credit": -1*balance if balance < 0 else 0 } + + if balance_in_account_currency != None: + balance_row.update({ + "debit_in_account_currency": balance_in_account_currency if balance_in_account_currency > 0 else 0, + "credit_in_account_currency": -1*balance_in_account_currency if balance_in_account_currency < 0 else 0 + }) + + return balance_row -def get_result_as_list(data): +def get_result_as_list(data, filters): result = [] for d in data: - result.append([d.get("posting_date"), d.get("account"), d.get("debit"), - d.get("credit"), d.get("voucher_type"), d.get("voucher_no"), - d.get("against"), d.get("party_type"), d.get("party"), - d.get("cost_center"), d.get("remarks")]) + row = [d.get("posting_date"), d.get("account"), d.get("debit"), d.get("credit")] + + if filters.get("show_in_account_currency"): + row += [d.get("debit_in_account_currency"), d.get("credit_in_account_currency")] + + row += [d.get("voucher_type"), d.get("voucher_no"), d.get("against"), + d.get("party_type"), d.get("party"), d.get("cost_center"), d.get("remarks") + ] + + result.append(row) return result diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 51c79160c7a..89ff6cff15d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -50,7 +50,7 @@ def validate_fiscal_year(date, fiscal_year, label=_("Date"), doc=None): throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year)) @frappe.whitelist() -def get_balance_on(account=None, date=None, party_type=None, party=None): +def get_balance_on(account=None, date=None, party_type=None, party=None, in_account_currency=True): if not account and frappe.form_dict.get("account"): account = frappe.form_dict.get("account") if not date and frappe.form_dict.get("date"): @@ -102,10 +102,14 @@ def get_balance_on(account=None, date=None, party_type=None, party=None): (party_type.replace('"', '\\"'), party.replace('"', '\\"'))) if account or (party_type and party): + if in_account_currency: + select_field = "sum(ifnull(debit_in_account_currency, 0)) - sum(ifnull(credit_in_account_currency, 0))" + else: + select_field = "sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))" bal = frappe.db.sql(""" - SELECT sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + SELECT {0} FROM `tabGL Entry` gle - WHERE %s""" % " and ".join(cond))[0][0] + WHERE {1}""".format(select_field, " and ".join(cond)))[0][0] # if bal is None, return 0 return flt(bal) @@ -194,21 +198,26 @@ def update_against_doc(d, jv_obj): jv_detail.set("reference_name", d["against_voucher"]) if d['allocated_amt'] < d['unadjusted_amt']: - jvd = frappe.db.sql("""select cost_center, balance, against_account, is_advance - from `tabJournal Entry Account` where name = %s""", d['voucher_detail_no']) + jvd = frappe.db.sql(""" + select cost_center, balance, against_account, is_advance, account_type, exchange_rate + from `tabJournal Entry Account` where name = %s + """, d['voucher_detail_no'], as_dict=True) + # new entry with balance amount ch = jv_obj.append("accounts") ch.account = d['account'] + ch.account_type = jvd[0]['account_type'] + ch.exchange_rate = jvd[0]['exchange_rate'] ch.party_type = d["party_type"] ch.party = d["party"] - ch.cost_center = cstr(jvd[0][0]) - ch.balance = flt(jvd[0][1]) + ch.cost_center = cstr(jvd[0]["cost_center"]) + ch.balance = flt(jvd[0]["balance"]) ch.set(d['dr_or_cr'], flt(d['unadjusted_amt']) - flt(d['allocated_amt'])) ch.set(d['dr_or_cr']== 'debit' and 'credit' or 'debit', 0) - ch.against_account = cstr(jvd[0][2]) + ch.against_account = cstr(jvd[0]["against_account"]) ch.reference_type = original_reference_type ch.reference_name = original_reference_name - ch.is_advance = cstr(jvd[0][3]) + ch.is_advance = cstr(jvd[0]["is_advance"]) ch.docstatus = 1 # will work as update after submit @@ -273,7 +282,7 @@ def get_stock_and_account_difference(account_list=None, posting_date=None): and name in (%s)""" % ', '.join(['%s']*len(account_list)), account_list)) for account, warehouse in account_warehouse.items(): - account_balance = get_balance_on(account, posting_date) + account_balance = get_balance_on(account, posting_date, in_account_currency=False) stock_value = get_stock_value_on(warehouse, posting_date) if abs(flt(stock_value) - flt(account_balance)) > 0.005: difference.setdefault(account, flt(stock_value) - flt(account_balance)) @@ -378,7 +387,7 @@ def get_stock_rbnb_difference(posting_date, company): # Balance as per system stock_rbnb_account = "Stock Received But Not Billed - " + frappe.db.get_value("Company", company, "abbr") - sys_bal = get_balance_on(stock_rbnb_account, posting_date) + sys_bal = get_balance_on(stock_rbnb_account, posting_date, in_account_currency=False) # Amount should be credited return flt(stock_rbnb) + flt(sys_bal) diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.js b/erpnext/buying/doctype/purchase_common/purchase_common.js index 7693e080946..f372cd84d8d 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.js +++ b/erpnext/buying/doctype/purchase_common/purchase_common.js @@ -157,20 +157,9 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ }, add_deduct_tax: function(doc, cdt, cdn) { this.calculate_taxes_and_totals(); - }, - - calculate_outstanding_amount: function() { - if(this.frm.doc.doctype == "Purchase Invoice" && this.frm.doc.docstatus < 2) { - frappe.model.round_floats_in(this.frm.doc, ["base_grand_total", "total_advance", "write_off_amount"]); - this.frm.doc.total_amount_to_pay = flt(this.frm.doc.base_grand_total - this.frm.doc.write_off_amount, - precision("total_amount_to_pay")); - if (!this.frm.doc.is_return) { - this.frm.doc.outstanding_amount = flt(this.frm.doc.total_amount_to_pay - this.frm.doc.total_advance, - precision("outstanding_amount")); - } - } } }); + cur_frm.add_fetch('project_name', 'cost_center', 'cost_center'); erpnext.buying.get_default_bom = function(frm) { diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 14214036596..4a8676f5782 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -389,8 +389,31 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "depends_on": "eval:!doc.__islocal", - "description": "Mention if non-standard receivable account applicable", + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Accounting Currency", + "no_copy": 0, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "", + "description": "Mention if non-standard receivable account", "fieldname": "accounts", "fieldtype": "Table", "hidden": 0, diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index af7716be0ea..6ee6b3928b1 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -8,6 +8,7 @@ from frappe import msgprint, _ from frappe.model.naming import make_autoname from erpnext.utilities.address_and_contact import load_address_and_contact from erpnext.utilities.transaction_base import TransactionBase +from erpnext.accounts.party import validate_accounting_currency, validate_party_account class Supplier(TransactionBase): def get_feed(self): @@ -44,6 +45,9 @@ class Supplier(TransactionBase): if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series': if not self.naming_series: msgprint(_("Series is mandatory"), raise_exception=1) + + validate_accounting_currency(self) + validate_party_account(self) def get_contacts(self,nm): if nm: diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json index dfa5d46f24a..6d01dffb614 100644 --- a/erpnext/buying/doctype/supplier/test_records.json +++ b/erpnext/buying/doctype/supplier/test_records.json @@ -1,14 +1,22 @@ [ { - "company": "_Test Company", "doctype": "Supplier", "supplier_name": "_Test Supplier", "supplier_type": "_Test Supplier Type" }, { - "company": "_Test Company", "doctype": "Supplier", "supplier_name": "_Test Supplier 1", "supplier_type": "_Test Supplier Type" + }, + { + "doctype": "Supplier", + "supplier_name": "_Test Supplier USD", + "supplier_type": "_Test Supplier Type", + "party_account_currency": "USD", + "accounts": [{ + "company": "_Test Company", + "account": "_Test Payable USD - _TC" + }] } -] +] \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c7600dfaeac..ceb993096ac 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -14,8 +14,19 @@ from erpnext.controllers.sales_and_purchase_return import validate_return force_item_fields = ("item_group", "barcode", "brand", "stock_uom") class CustomerFrozen(frappe.ValidationError): pass +class InvalidCurrency(frappe.ValidationError): pass 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) @@ -39,6 +50,7 @@ class AccountsController(TransactionBase): self.validate_enabled_taxes_and_charges() self.validate_party() + self.validate_currency() def on_submit(self): if self.meta.get_field("is_recurring"): @@ -95,8 +107,6 @@ class AccountsController(TransactionBase): def set_price_list_currency(self, buying_or_selling): if self.meta.get_field("currency"): - company_currency = get_company_currency(self.company) - # price list part fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ else "buying_price_list" @@ -104,22 +114,22 @@ class AccountsController(TransactionBase): self.price_list_currency = frappe.db.get_value("Price List", self.get(fieldname), "currency") - if self.price_list_currency == company_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, company_currency) + 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 == company_currency: + elif self.currency == self.company_currency: self.conversion_rate = 1.0 elif not self.conversion_rate: self.conversion_rate = get_exchange_rate(self.currency, - company_currency) + self.company_currency) def set_missing_item_details(self): """set missing item values""" @@ -156,7 +166,7 @@ class AccountsController(TransactionBase): 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")) - + def set_taxes(self): if not self.meta.get_field("taxes"): @@ -187,7 +197,7 @@ class AccountsController(TransactionBase): 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): + def get_gl_dict(self, args, account_currency=None): """this method populates the common properties of a gl entry record""" gl_dict = frappe._dict({ 'company': self.company, @@ -198,11 +208,51 @@ class AccountsController(TransactionBase): 'fiscal_year': self.fiscal_year, '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 }) gl_dict.update(args) + + if not account_currency: + account_currency = frappe.db.get_value("Account", gl_dict.account, "account_currency") + + self.validate_account_currency(gl_dict.account, account_currency) + gl_dict = self.set_balance_in_account_currency(gl_dict, account_currency) + + return gl_dict + + def validate_account_currency(self, account, account_currency=None): + if self.doctype == "Journal Entry": + return + 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 set_balance_in_account_currency(self, gl_dict, account_currency=None): + if (not self.get("conversion_rate") and self.doctype!="Journal Entry" + and account_currency!=self.company_currency): + frappe.throw(_("Account: {0} with currency: {1} can not be selected") + .format(gl_dict.account, account_currency)) + + gl_dict["account_currency"] = self.company_currency if account_currency==self.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==self.company_currency \ + else flt(gl_dict.debit / (self.get("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==self.company_currency \ + else flt(gl_dict.credit / (self.get("conversion_rate")), 2) + return gl_dict def clear_unallocated_advances(self, childtype, parentfield): @@ -217,13 +267,13 @@ class AccountsController(TransactionBase): # conver sales_order to "Sales Order" reference_type = against_order_field.replace("_", " ").title() - + condition = "" if order_list: in_placeholder = ', '.join(['%s'] * len(order_list)) condition = "or (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))"\ .format(reference_type, in_placeholder) - + res = frappe.db.sql(""" select t1.name as jv_no, t1.remark, t2.{0} as amount, t2.name as jv_detail_no, @@ -321,9 +371,9 @@ class AccountsController(TransactionBase): def set_total_advance_paid(self): if self.doctype == "Sales Order": - dr_or_cr = "credit" + dr_or_cr = "credit_in_account_currency" else: - dr_or_cr = "debit" + dr_or_cr = "debit_in_account_currency" advance_paid = frappe.db.sql(""" select @@ -331,10 +381,9 @@ class AccountsController(TransactionBase): from `tabJournal Entry Account` where - reference_type = %s and - reference_name = %s and - docstatus = 1 and is_advance = "Yes" """.format(dr_or_cr=dr_or_cr), - (self.doctype, self.name)) + reference_type = %s and reference_name = %s + and docstatus = 1 and is_advance = "Yes" + """.format(dr_or_cr=dr_or_cr), (self.doctype, self.name)) if advance_paid: advance_paid = flt(advance_paid[0][0], self.precision("advance_paid")) @@ -357,6 +406,13 @@ class AccountsController(TransactionBase): if frozen_accounts_modifier in frappe.get_roles(): return + party_type, party = self.get_party() + + if party_type: + if frappe.db.get_value(party_type, party, "is_frozen"): + frappe.throw("{0} {1} is frozen".format(party_type, party), CustomerFrozen) + + def get_party(self): party_type = None if self.meta.get_field("customer"): party_type = 'Customer' @@ -364,10 +420,20 @@ class AccountsController(TransactionBase): elif self.meta.get_field("supplier"): party_type = 'Supplier' - if party_type: - party = self.get(party_type.lower()) - if frappe.db.get_value(party_type, party, "is_frozen"): - frappe.throw("{0} {1} is frozen".format(party_type, party), CustomerFrozen) + 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 = frappe.db.get_value(party_type, party, "party_account_currency") \ + or self.company_currency + + if 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) @frappe.whitelist() def get_tax_rate(account_head): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 19b5a9b403e..eccceb0ad3b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -46,22 +46,22 @@ class StockController(AccountsController): # from warehouse account self.check_expense_account(detail) - + gl_list.append(self.get_gl_dict({ - "account": warehouse_account[sle.warehouse], + "account": warehouse_account[sle.warehouse]["name"], "against": detail.expense_account, "cost_center": detail.cost_center, "remarks": self.get("remarks") or "Accounting Entry for Stock", - "debit": flt(sle.stock_value_difference, 2) - })) + "debit": flt(sle.stock_value_difference, 2), + }, warehouse_account[sle.warehouse]["account_currency"])) - # to target warehouse / expense account + # to target warehouse / expense account gl_list.append(self.get_gl_dict({ "account": detail.expense_account, - "against": warehouse_account[sle.warehouse], + "against": warehouse_account[sle.warehouse]["name"], "cost_center": detail.cost_center, "remarks": self.get("remarks") or "Accounting Entry for Stock", - "credit": flt(sle.stock_value_difference, 2) + "credit": flt(sle.stock_value_difference, 2), })) elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) @@ -69,7 +69,7 @@ class StockController(AccountsController): if warehouse_with_no_account: msgprint(_("No accounting entries for the following warehouses") + ": \n" + "\n".join(warehouse_with_no_account)) - + return process_gl_map(gl_list) def get_voucher_details(self, default_expense_account, default_cost_center, sle_map): @@ -336,6 +336,9 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): return gl_entries def get_warehouse_account(): - warehouse_account = dict(frappe.db.sql("""select warehouse, name from tabAccount - where account_type = 'Warehouse' and ifnull(warehouse, '') != ''""")) + warehouse_account = frappe._dict() + + for d in frappe.db.sql("""select warehouse, name, account_currency from tabAccount + where account_type = 'Warehouse' and ifnull(warehouse, '') != ''""", as_dict=1): + warehouse_account.setdefault(d.warehouse, d) return warehouse_account diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index d526f66baf3..0fbe22d7af5 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -394,17 +394,21 @@ class calculate_taxes_and_totals(object): # NOTE: # write_off_amount is only for POS Invoice # total_advance is only for non POS Invoice - + if self.doc.is_return: + return + + self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) + total_amount_to_pay = flt(self.doc.grand_total - self.doc.total_advance - self.doc.write_off_amount, + self.doc.precision("grand_total")) + if self.doc.doctype == "Sales Invoice": - if not self.doc.is_return: - self.doc.round_floats_in(self.doc, ["base_grand_total", "total_advance", "write_off_amount", "paid_amount"]) - total_amount_to_pay = self.doc.base_grand_total - self.doc.write_off_amount - self.doc.outstanding_amount = flt(total_amount_to_pay - self.doc.total_advance - self.doc.paid_amount, - self.doc.precision("outstanding_amount")) + self.doc.round_floats_in(self.doc, ["paid_amount"]) + outstanding_amount = flt(total_amount_to_pay - self.doc.paid_amount, self.doc.precision("outstanding_amount")) + elif self.doc.doctype == "Purchase Invoice": + outstanding_amount = flt(total_amount_to_pay, self.doc.precision("outstanding_amount")) + + if self.doc.party_account_currency == self.doc.currency: + self.doc.outstanding_amount = outstanding_amount else: - self.doc.round_floats_in(self.doc, ["total_advance", "write_off_amount"]) - self.doc.total_amount_to_pay = flt(self.doc.base_grand_total - self.doc.write_off_amount, - self.doc.precision("total_amount_to_pay")) - if not self.doc.is_return: - self.doc.outstanding_amount = flt(self.doc.total_amount_to_pay - self.doc.total_advance, - self.doc.precision("outstanding_amount")) + self.doc.outstanding_amount = flt(outstanding_amount * self.doc.conversion_rate, + self.doc.precision("outstanding_amount")) \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 78729a3bcbb..5cf96e99044 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -6,6 +6,7 @@ import frappe, json from frappe.utils import cstr, cint from frappe import msgprint, _ from frappe.model.mapper import get_mapped_doc +from erpnext.setup.utils import get_exchange_rate from erpnext.utilities.transaction_base import TransactionBase @@ -179,7 +180,17 @@ def get_item_details(item_code): def make_quotation(source_name, target_doc=None): def set_missing_values(source, target): quotation = frappe.get_doc(target) - quotation.currency = None # set it as default from customer + + company_currency = frappe.db.get_value("Company", quotation.company, "default_currency") + party_account_currency = frappe.db.get_value("Customer", quotation.customer, "party_account_currency") + if company_currency == party_account_currency: + exchange_rate = 1 + else: + exchange_rate = get_exchange_rate(party_account_currency, company_currency) + + quotation.currency = party_account_currency or company_currency + quotation.conversion_rate = exchange_rate + quotation.run_method("set_missing_values") quotation.run_method("calculate_taxes_and_totals") diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2b4a0171389..ffb7a21b5f9 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -205,4 +205,5 @@ erpnext.patches.v6_0.default_activity_rate execute:frappe.db.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1) execute:frappe.db.sql("""update `tabProject` set percent_complete=round(percent_complete, 2) where percent_complete is not null""") erpnext.patches.v6_0.fix_outstanding_amount -erpnext.patches.v6_0.fix_planned_qty \ No newline at end of file +erpnext.patches.v6_0.fix_planned_qty +erpnext.patches.v6_0.multi_currency \ No newline at end of file diff --git a/erpnext/patches/v6_0/multi_currency.py b/erpnext/patches/v6_0/multi_currency.py new file mode 100644 index 00000000000..2b53134c050 --- /dev/null +++ b/erpnext/patches/v6_0/multi_currency.py @@ -0,0 +1,106 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + # Reload doctype + for dt in ("Account", "GL Entry", "Journal Entry", + "Journal Entry Account", "Sales Invoice", "Purchase Invoice", "Customer", "Supplier"): + frappe.reload_doctype(dt) + + for company in frappe.get_all("Company", fields=["name", "default_currency", "default_receivable_account"]): + + # update currency in account and gl entry as per company currency + frappe.db.sql("""update `tabAccount` set account_currency = %s + where ifnull(account_currency, '') = '' and company=%s""", (company.default_currency, company.name)) + + # update newly introduced field's value in sales / purchase invoice + frappe.db.sql(""" + update + `tabSales Invoice` + set + base_paid_amount=paid_amount, + base_write_off_amount=write_off_amount, + party_account_currency=%s + where company=%s + """, (company.default_currency, company.name)) + + frappe.db.sql(""" + update + `tabPurchase Invoice` + set + base_write_off_amount=write_off_amount, + party_account_currency=%s + where company=%s + """, (company.default_currency, company.name)) + + # update exchange rate, debit/credit in account currency in Journal Entry + frappe.db.sql(""" + update `tabJournal Entry Account` jea + set exchange_rate=1, + debit_in_account_currency=debit, + credit_in_account_currency=credit, + account_type=(select account_type from `tabAccount` where name=jea.account) + """) + + frappe.db.sql(""" + update `tabJournal Entry Account` jea, `tabJournal Entry` je + set account_currency=%s + where jea.parent = je.name and je.company=%s + """, (company.default_currency, company.name)) + + # update debit/credit in account currency in GL Entry + frappe.db.sql(""" + update + `tabGL Entry` + set + debit_in_account_currency=debit, + credit_in_account_currency=credit, + account_currency=%s + where + company=%s + """, (company.default_currency, company.name)) + + # Set party account if default currency of party other than company's default currency + for dt in ("Customer", "Supplier"): + parties = frappe.get_all(dt) + for p in parties: + # Get party GL Entries + party_gle = frappe.db.get_value("GL Entry", {"party_type": dt, "party": p.name, + "company": company.name}, ["account", "account_currency"], as_dict=True) + + party = frappe.get_doc(dt, p.name) + + # set party account currency + if party_gle or not party.party_account_currency: + party.party_account_currency = company.default_currency + + # Add default receivable /payable account if not exists + # and currency is other than company currency + if party.party_account_currency and party.party_account_currency != company.default_currency: + party_account_exists = False + for d in party.get("accounts"): + if d.company == company.name: + party_account_exists = True + break + + if not party_account_exists: + party_account = None + if party_gle: + party_account = party_gle.account + else: + default_receivable_account_currency = frappe.db.get_value("Account", + company.default_receivable_account, "account_currency") + if default_receivable_account_currency != company.default_currency: + party_account = company.default_receivable_account + + if party_account: + party.append("accounts", { + "company": company.name, + "account": party_account + }) + + party.flags.ignore_mandatory = True + party.save() \ No newline at end of file diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 0b3ca7f2a85..b1b24cf38ae 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -491,5 +491,45 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ this.frm.doc.total_advance = flt(total_allocated_amount, precision("total_advance")); this.calculate_outstanding_amount(update_paid_amount); + }, + + calculate_outstanding_amount: function(update_paid_amount) { + // NOTE: + // paid_amount and write_off_amount is only for POS Invoice + // total_advance is only for non POS Invoice + if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; + + frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); + + var total_amount_to_pay = flt((this.frm.doc.grand_total - this.frm.doc.total_advance + - this.frm.doc.write_off_amount), precision("grand_total")); + + if(this.frm.doc.doctype == "Sales Invoice") { + frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]); + + if(this.frm.doc.is_pos) { + if(!this.frm.doc.paid_amount || update_paid_amount===undefined || update_paid_amount) { + this.frm.doc.paid_amount = flt(total_amount_to_pay); + } + } else { + this.frm.doc.paid_amount = 0 + } + this.set_in_company_currency(this.frm.doc, ["paid_amount"]); + this.frm.refresh_field("paid_amount"); + this.frm.refresh_field("base_paid_amount"); + + var outstanding_amount = flt(total_amount_to_pay - this.frm.doc.paid_amount, + precision("outstanding_amount")); + + } else if(this.frm.doc.doctype == "Purchase Invoice") { + var outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount")); + } + + if(this.frm.doc.party_account_currency == this.frm.doc.currency) { + this.frm.set_value("outstanding_amount", outstanding_amount); + } else { + this.frm.set_value("outstanding_amount", + flt(outstanding_amount * this.frm.doc.conversion_rate, precision("outstanding_amount"))); + } } }) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c6b26bc48db..7f2cee760a9 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -423,11 +423,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ setup_field_label_map(["base_total", "base_net_total", "base_total_taxes_and_charges", "base_discount_amount", "base_grand_total", "base_rounded_total", "base_in_words", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", "total_amount_to_pay", - "outstanding_amount", "total_advance", "paid_amount", "write_off_amount"], company_currency); + "base_paid_amount", "base_write_off_amount" + ], company_currency); setup_field_label_map(["total", "net_total", "total_taxes_and_charges", "discount_amount", "grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted", - "rounded_total", "in_words"], this.frm.doc.currency); + "rounded_total", "in_words", "paid_amount", "write_off_amount"], this.frm.doc.currency); + + setup_field_label_map(["outstanding_amount", "total_advance"], this.frm.doc.party_account_currency); cur_frm.set_df_property("conversion_rate", "description", "1 " + this.frm.doc.currency + " = [?] " + company_currency) @@ -440,7 +443,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ // toggle fields this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total", "base_total_taxes_and_charges", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", - "base_grand_total", "base_rounded_total", "base_in_words", "base_discount_amount"], + "base_grand_total", "base_rounded_total", "base_in_words", "base_discount_amount", + "base_paid_amount", "base_write_off_amount"], this.frm.doc.currency != company_currency); this.frm.toggle_display(["plc_conversion_rate", "price_list_currency"], diff --git a/erpnext/public/js/pos/pos.js b/erpnext/public/js/pos/pos.js index 5f827964f6d..02eeab08379 100644 --- a/erpnext/public/js/pos/pos.js +++ b/erpnext/public/js/pos/pos.js @@ -473,13 +473,11 @@ erpnext.pos.PointOfSale = Class.extend({ } me.frm.set_value("mode_of_payment", values.mode_of_payment); - //me.frm.cscript.calculate_taxes_and_totals(); - - var paid_amount = flt((flt(values.paid_amount) - flt(values.change)) / me.frm.doc.conversion_rate, precision("paid_amount")); + var paid_amount = flt((flt(values.paid_amount) - flt(values.change)), precision("paid_amount")); me.frm.set_value("paid_amount", paid_amount); - + // specifying writeoff amount here itself, so as to avoid recursion issue - me.frm.set_value("write_off_amount", me.frm.doc.base_grand_total - paid_amount); + me.frm.set_value("write_off_amount", me.frm.doc.grand_total - paid_amount); me.frm.set_value("outstanding_amount", 0); me.frm.savesubmit(this); diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index ac0f6366cd5..9325e72ae7d 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -12,7 +12,15 @@ frappe.ui.form.on("Customer", "refresh", function(frm) { frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); - if(!frm.doc.__islocal) erpnext.utils.render_address_and_contact(frm); + if(!frm.doc.__islocal) { + erpnext.utils.render_address_and_contact(frm); + } + + var grid = cur_frm.get_field("sales_team").grid; + grid.set_column_disp("allocated_percentage", false); + grid.set_column_disp("allocated_amount", false); + grid.set_column_disp("incentives", false); + }) cur_frm.cscript.onload = function(doc, dt, dn) { @@ -92,11 +100,17 @@ cur_frm.fields_dict['default_price_list'].get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; + var filters = { + 'account_type': 'Receivable', + 'company': d.company, + "is_group": 0 + }; + + if(doc.party_account_currency) { + $.extend(filters, {"account_currency": doc.party_account_currency}); + } + return { - filters: { - 'account_type': 'Receivable', - 'company': d.company, - "is_group": 0 - } + filters: filters } } diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 4a8baaf4d63..d04b8b63a2c 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -273,7 +273,7 @@ "ignore_user_permissions": 1, "in_filter": 0, "in_list_view": 0, - "label": "Currency", + "label": "Default Currency", "no_copy": 1, "options": "Currency", "permlevel": 0, @@ -463,8 +463,31 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "depends_on": "eval:!doc.__islocal", - "description": "Mention if non-standard receivable account applicable", + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Accounting Currency", + "no_copy": 0, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "", + "description": "Mention if non-standard receivable account", "fieldname": "accounts", "fieldtype": "Table", "hidden": 0, @@ -567,7 +590,7 @@ "no_copy": 0, "oldfieldname": "credit_limit", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "", "permlevel": 1, "print_hide": 0, "read_only": 0, diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 5cbf243fb0e..7ea90f07544 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -10,6 +10,7 @@ from frappe.utils import flt from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.address_and_contact import load_address_and_contact +from erpnext.accounts.party import validate_accounting_currency, validate_party_account from frappe.desk.reportview import build_match_conditions class Customer(TransactionBase): @@ -27,12 +28,14 @@ class Customer(TransactionBase): else: self.name = make_autoname(self.naming_series+'.#####') - def validate_values(self): + def validate_mandatory(self): if frappe.defaults.get_global_default('cust_master_name') == 'Naming Series' and not self.naming_series: frappe.throw(_("Series is mandatory"), frappe.MandatoryError) - + def validate(self): - self.validate_values() + self.validate_mandatory() + validate_accounting_currency(self) + validate_party_account(self) def update_lead_status(self): if self.lead_name: diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index dca4bb76fb8..a0a1501a467 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -8,6 +8,8 @@ import unittest from frappe.test_runner import make_test_records from erpnext.controllers.accounts_controller import CustomerFrozen +from erpnext.accounts.party import InvalidCurrency +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice test_ignore = ["Price List"] @@ -77,4 +79,14 @@ class TestCustomer(unittest.TestCase): frappe.db.set_value("Customer", "_Test Customer", "is_frozen", 0) - so.save() \ No newline at end of file + so.save() + + def test_multi_currency(self): + customer = frappe.get_doc("Customer", "_Test Customer USD") + + create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", + currency="USD", conversion_rate=50) + + customer.party_account_currency = "EUR" + self.assertRaises(InvalidCurrency, customer.save) + \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/test_records.json b/erpnext/selling/doctype/customer/test_records.json index e076f7a8931..060633ec4f6 100644 --- a/erpnext/selling/doctype/customer/test_records.json +++ b/erpnext/selling/doctype/customer/test_records.json @@ -1,6 +1,5 @@ [ { - "company": "_Test Company", "customer_group": "_Test Customer Group", "customer_name": "_Test Customer", "customer_type": "Individual", @@ -8,7 +7,6 @@ "territory": "_Test Territory" }, { - "company": "_Test Company", "customer_group": "_Test Customer Group", "customer_name": "_Test Customer 1", "customer_type": "Individual", @@ -16,7 +14,6 @@ "territory": "_Test Territory" }, { - "company": "_Test Company", "customer_group": "_Test Customer Group", "customer_name": "_Test Customer 2", "customer_type": "Individual", @@ -24,11 +21,22 @@ "territory": "_Test Territory" }, { - "company": "_Test Company", "customer_group": "_Test Customer Group", "customer_name": "_Test Customer 3", "customer_type": "Individual", "doctype": "Customer", "territory": "_Test Territory" + }, + { + "customer_group": "_Test Customer Group", + "customer_name": "_Test Customer USD", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory", + "party_account_currency": "USD", + "accounts": [{ + "company": "_Test Company", + "account": "_Test Receivable USD - _TC" + }] } ] diff --git a/erpnext/selling/doctype/sales_team/sales_team.json b/erpnext/selling/doctype/sales_team/sales_team.json index dfd0ff192b4..1747d2ed923 100644 --- a/erpnext/selling/doctype/sales_team/sales_team.json +++ b/erpnext/selling/doctype/sales_team/sales_team.json @@ -33,31 +33,6 @@ "unique": 0, "width": "200px" }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "sales_designation", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Designation", - "no_copy": 0, - "oldfieldname": "sales_designation", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_width": "100px", - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "100px" - }, { "allow_on_submit": 0, "bold": 0, @@ -210,7 +185,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2013-12-31 19:00:14", + "modified": "2015-08-13 16:30:24.146848", "modified_by": "Administrator", "module": "Selling", "name": "Sales Team", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 5404947e1d9..6a8744a3854 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -206,30 +206,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ } }, - calculate_outstanding_amount: function(update_paid_amount) { - // NOTE: - // paid_amount and write_off_amount is only for POS Invoice - // total_advance is only for non POS Invoice - if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.docstatus==0 && !this.frm.doc.is_return) { - frappe.model.round_floats_in(this.frm.doc, ["base_grand_total", "total_advance", "write_off_amount", - "paid_amount"]); - var total_amount_to_pay = this.frm.doc.base_grand_total - this.frm.doc.write_off_amount - - this.frm.doc.total_advance; - if(this.frm.doc.is_pos) { - if(!this.frm.doc.paid_amount || update_paid_amount===undefined || update_paid_amount) { - this.frm.doc.paid_amount = flt(total_amount_to_pay); - this.frm.refresh_field("paid_amount"); - } - } else { - this.frm.doc.paid_amount = 0 - this.frm.refresh_field("paid_amount"); - } - - this.frm.set_value("outstanding_amount", flt(total_amount_to_pay - - this.frm.doc.paid_amount, precision("outstanding_amount"))); - } - }, - calculate_commission: function() { if(this.frm.fields_dict.commission_rate) { if(this.frm.doc.commission_rate > 100) { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index cf47de5f6f6..804dc4deb93 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -34,13 +34,8 @@ class Company(Document): if not self.abbr.strip(): frappe.throw(_("Abbreviation is mandatory")) - self.previous_default_currency = frappe.db.get_value("Company", self.name, "default_currency") - if self.default_currency and self.previous_default_currency and \ - self.default_currency != self.previous_default_currency and \ - self.check_if_transactions_exist(): - frappe.throw(_("Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency.")) - self.validate_default_accounts() + self.validate_currency() def validate_default_accounts(self): for field in ["default_bank_account", "default_cash_account", "default_receivable_account", "default_payable_account", @@ -51,6 +46,13 @@ class Company(Document): if for_company != self.name: frappe.throw(_("Account {0} does not belong to company: {1}") .format(self.get(field), self.name)) + + def validate_currency(self): + self.previous_default_currency = frappe.db.get_value("Company", self.name, "default_currency") + if self.default_currency and self.previous_default_currency and \ + self.default_currency != self.previous_default_currency and \ + self.check_if_transactions_exist(): + frappe.throw(_("Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency.")) def on_update(self): if not frappe.db.sql("""select name from tabAccount diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index 13cb03e0816..b6918b3afaa 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -19,7 +19,7 @@ }, { "abbr": "_TC2", - "company_name": "_Test Company 3", + "company_name": "_Test Company 2", "default_currency": "EUR", "country": "Germany", "doctype": "Company", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index e7ede652a3a..17194fdd4a7 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -10,17 +10,6 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( this._super(); if (!doc.is_return) { - if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1) { - // show Make Invoice button only if Delivery Note is not created from Sales Invoice - var from_sales_invoice = false; - from_sales_invoice = cur_frm.doc.items.some(function(item) { - return item.against_sales_invoice ? true : false; - }); - - if(!from_sales_invoice) - cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary"); - } - if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1) cur_frm.add_custom_button(__('Installation Note'), this.make_installation_note); @@ -59,7 +48,16 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( } } + if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1 && !doc.is_return) { + // show Make Invoice button only if Delivery Note is not created from Sales Invoice + var from_sales_invoice = false; + from_sales_invoice = cur_frm.doc.items.some(function(item) { + return item.against_sales_invoice ? true : false; + }); + if(!from_sales_invoice) + cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary"); + } erpnext.stock.delivery_note.set_print_hide(doc, dt, dn); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1ab866c7733..85610a49d21 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -307,23 +307,28 @@ class PurchaseReceipt(BuyingController): val_rate_db_precision = 6 if cint(d.precision("valuation_rate")) <= 6 else 9 # warehouse account + stock_value_diff = flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty) + * flt(d.conversion_factor), d.precision("base_net_amount")) + gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[d.warehouse], + "account": warehouse_account[d.warehouse]["name"], "against": stock_rbnb, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty) * flt(d.conversion_factor), - self.precision("base_net_amount", d)) - })) + "debit": stock_value_diff + }, warehouse_account[d.warehouse]["account_currency"])) # stock received but not billed + stock_rbnb_currency = frappe.db.get_value("Account", stock_rbnb, "account_currency") gl_entries.append(self.get_gl_dict({ "account": stock_rbnb, - "against": warehouse_account[d.warehouse], + "against": warehouse_account[d.warehouse]["name"], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(d.base_net_amount, self.precision("base_net_amount", d)) - })) + "credit": flt(d.base_net_amount, d.precision("base_net_amount")), + "credit_in_account_currency": flt(d.base_net_amount, d.precision("base_net_amount")) \ + if stock_rbnb_currency==self.company_currency else flt(d.net_amount, d.precision("net_amount")) + }, stock_rbnb_currency)) negative_expense_to_be_booked += flt(d.item_tax_amount) @@ -331,7 +336,7 @@ class PurchaseReceipt(BuyingController): if flt(d.landed_cost_voucher_amount): gl_entries.append(self.get_gl_dict({ "account": expenses_included_in_valuation, - "against": warehouse_account[d.warehouse], + "against": warehouse_account[d.warehouse]["name"], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(d.landed_cost_voucher_amount) @@ -340,12 +345,12 @@ class PurchaseReceipt(BuyingController): # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[self.supplier_warehouse], - "against": warehouse_account[d.warehouse], + "account": warehouse_account[self.supplier_warehouse]["name"], + "against": warehouse_account[d.warehouse]["name"], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(d.rm_supp_cost) - })) + }, warehouse_account[self.supplier_warehouse]["account_currency"])) # divisional loss adjustment sle_valuation_amount = flt(flt(d.valuation_rate, val_rate_db_precision) * flt(d.qty) * flt(d.conversion_factor), @@ -358,11 +363,11 @@ class PurchaseReceipt(BuyingController): if divisional_loss: gl_entries.append(self.get_gl_dict({ "account": stock_rbnb, - "against": warehouse_account[d.warehouse], + "against": warehouse_account[d.warehouse]["name"], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": divisional_loss - })) + }, stock_rbnb_currency)) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: