From 46f3c65b003442ecfcdbcdc1d13422f872ea88bf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Dec 2020 23:09:19 +0530 Subject: [PATCH 01/11] feat: Multi currency in landed cost voucher --- .../landed_cost_taxes_and_charges.json | 32 +++++++++- .../landed_cost_voucher.js | 58 +++++++++++++++++-- .../landed_cost_voucher.json | 26 ++++++++- .../landed_cost_voucher.py | 31 ++++++---- 4 files changed, 129 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 0cc243d4cb..ae8747fc83 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -1,12 +1,16 @@ { + "actions": [], "creation": "2014-07-11 11:51:00.453717", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "expense_account", + "account_currency", + "exchange_rate", "description", "col_break3", + "base_amount", "amount" ], "fields": [ @@ -27,7 +31,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Amount", - "options": "Company:company:default_currency", + "options": "account_currency", "reqd": 1 }, { @@ -37,10 +41,34 @@ "label": "Expense Account", "options": "Account", "reqd": 1 + }, + { + "fieldname": "account_currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "precision": "9" + }, + { + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Base Amount", + "options": "Company:company:default_currency", + "read_only": 1, + "reqd": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-09-30 18:28:32.070655", + "links": [], + "modified": "2020-12-13 21:04:01.769989", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Taxes and Charges", diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 5de1352518..a5ac60c4f5 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -32,9 +32,8 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ this.frm.set_query("expense_account", "taxes", function() { return { - query: "erpnext.controllers.queries.tax_account_query", filters: { - "account_type": ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"], + "account_type": ['in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]], "company": me.frm.doc.company } }; @@ -97,7 +96,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ set_total_taxes_and_charges: function() { var total_taxes_and_charges = 0.0; $.each(this.frm.doc.taxes || [], function(i, d) { - total_taxes_and_charges += flt(d.amount) + total_taxes_and_charges += flt(d.base_amount) }); cur_frm.set_value("total_taxes_and_charges", total_taxes_and_charges); }, @@ -134,7 +133,58 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ items_remove: () => { this.trigger('set_applicable_charges_for_item'); } - }); cur_frm.script_manager.make(erpnext.stock.LandedCostVoucher); + +frappe.ui.form.on('Landed Cost Voucher', { + set_account_currency: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.expense_account) { + frappe.db.get_value('Account', row.expense_account, 'account_currency', function(value) { + frappe.model.set_value(cdt, cdn, "account_currency", value.account_currency); + frm.events.set_exchange_rate(frm, cdt, cdn); + }); + } + }, + + set_exchange_rate: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + + if(row.account_currency == company_currency) { + row.exchange_rate = 1; + } else if (!row.exchange_rate || row.exchange_rate == 1) { + frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", + args: { + posting_date: frm.doc.posting_date, + account: row.expense_account, + account_currency: row.account_currency, + company: frm.doc.company + }, + callback: function(r) { + if(r.message) { + frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); + } + } + }) + } + }, + + set_base_amount: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, "base_amount", + flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row))); + } +}) + +frappe.ui.form.on('Landed Cost Taxes and Charges', { + expense_account: function(frm, cdt, cdn) { + frm.events.set_account_currency(frm, cdt, cdn); + }, + + amount: function(frm, cdt, cdn) { + frm.events.set_base_amount(frm, cdt, cdn); + } +}); diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json index 01492807de..6a7994ba9e 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2014-07-11 11:33:42.547339", "doctype": "DocType", @@ -7,6 +8,9 @@ "field_order": [ "naming_series", "company", + "column_break_2", + "posting_date", + "section_break_5", "purchase_receipts", "purchase_receipt_items", "get_items_from_purchase_receipts", @@ -86,7 +90,7 @@ { "fieldname": "total_taxes_and_charges", "fieldtype": "Currency", - "label": "Total Taxes and Charges", + "label": "Total Taxes and Charges (Company Currency)", "options": "Company:company:default_currency", "read_only": 1, "reqd": 1 @@ -119,11 +123,29 @@ "fieldname": "landed_cost_help", "fieldtype": "HTML", "label": "Landed Cost Help" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hide_border": 1 } ], "icon": "icon-usd", + "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2019-11-21 15:34:10.846093", + "links": [], + "modified": "2020-12-13 23:18:47.442466", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Voucher", diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index bc3d3266ad..e39208991b 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -9,6 +9,7 @@ from frappe.model.meta import get_field_precision from frappe.model.document import Document from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.accounts.doctype.account.account import get_account_currency +from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate class LandedCostVoucher(Document): def get_items_from_purchase_receipts(self): @@ -39,12 +40,13 @@ class LandedCostVoucher(Document): def validate(self): self.check_mandatory() + self.set_exchange_rate() + self.set_amounts_in_company_currency() if not self.get("items"): self.get_items_from_purchase_receipts() else: self.validate_applicable_charges_for_item() self.validate_purchase_receipts() - self.validate_expense_accounts() self.set_total_taxes_and_charges() def check_mandatory(self): @@ -73,16 +75,25 @@ class LandedCostVoucher(Document): frappe.throw(_("Row {0}: Cost center is required for an item {1}") .format(item.idx, item.item_code)) - def validate_expense_accounts(self): - company_currency = erpnext.get_company_currency(self.company) - for account in self.taxes: - if get_account_currency(account.expense_account) != company_currency: - frappe.throw(msg=_(""" Row {0}: Expense account currency should be same as company's default currency. - Please select expense account with account currency as {1}""") - .format(account.idx, frappe.bold(company_currency)), title=_("Invalid Account Currency")) - def set_total_taxes_and_charges(self): - self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")]) + self.total_taxes_and_charges = sum([flt(d.base_amount) for d in self.get("taxes")]) + + def set_exchange_rate(self): + company_currency = erpnext.get_company_currency(self.company) + for d in self.get('taxes'): + if d.account_currency == company_currency: + d.exchange_rate = 1 + elif not d.exchange_rate or d.exchange_rate == 1 or self.posting_date: + d.exchange_rate = get_exchange_rate(self.posting_date, account=d.expense_account, + account_currency=d.account_currency, company=self.company) + + if not d.exchange_rate: + frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx)) + + def set_amounts_in_company_currency(self): + for d in self.get('taxes'): + d.amount = flt(d.amount, d.precision("amount")) + d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) def validate_applicable_charges_for_item(self): based_on = self.distribute_charges_based_on.lower() From 5a2030c2f22e1af5eeedcab053978758826dd34b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Dec 2020 09:21:11 +0530 Subject: [PATCH 02/11] fix: Add server side methods and other fixes --- erpnext/controllers/accounts_controller.py | 2 +- .../landed_cost_voucher.js | 19 ++++--- .../landed_cost_voucher.py | 38 ++++++++++++-- .../test_landed_cost_voucher.py | 51 +++++++++++++++++-- .../purchase_receipt/purchase_receipt.py | 16 ++++-- 5 files changed, 106 insertions(+), 20 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 32c5d3a3b1..49c9f8c9d0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -442,7 +442,7 @@ class AccountsController(TransactionBase): account_currency = get_account_currency(gl_dict.account) if gl_dict.account and self.doctype not in ["Journal Entry", - "Period Closing Voucher", "Payment Entry"]: + "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice"]: self.validate_account_currency(gl_dict.account, account_currency) set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index a5ac60c4f5..82b5242704 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -96,9 +96,9 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ set_total_taxes_and_charges: function() { var total_taxes_and_charges = 0.0; $.each(this.frm.doc.taxes || [], function(i, d) { - total_taxes_and_charges += flt(d.base_amount) + total_taxes_and_charges += flt(d.base_amount); }); - cur_frm.set_value("total_taxes_and_charges", total_taxes_and_charges); + this.frm.set_value("total_taxes_and_charges", total_taxes_and_charges); }, set_applicable_charges_for_item: function() { @@ -148,11 +148,16 @@ frappe.ui.form.on('Landed Cost Voucher', { } }, + onload: function(frm) { + let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + frm.set_currency_labels(["total_taxes_and_charges"], company_currency); + }, + set_exchange_rate: function(frm, cdt, cdn) { let row = locals[cdt][cdn]; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - if(row.account_currency == company_currency) { + if (row.account_currency == company_currency) { row.exchange_rate = 1; } else if (!row.exchange_rate || row.exchange_rate == 1) { frappe.call({ @@ -164,12 +169,12 @@ frappe.ui.form.on('Landed Cost Voucher', { company: frm.doc.company }, callback: function(r) { - if(r.message) { + if (r.message) { frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); } } - }) - } + }); + }; }, set_base_amount: function(frm, cdt, cdn) { @@ -177,7 +182,7 @@ frappe.ui.form.on('Landed Cost Voucher', { frappe.model.set_value(cdt, cdn, "base_amount", flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row))); } -}) +}); frappe.ui.form.on('Landed Cost Taxes and Charges', { expense_account: function(frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index e39208991b..519077afa4 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -40,14 +40,17 @@ class LandedCostVoucher(Document): def validate(self): self.check_mandatory() + self.validate_purchase_receipts() + self.set_account_currency() self.set_exchange_rate() self.set_amounts_in_company_currency() + self.set_total_taxes_and_charges() if not self.get("items"): self.get_items_from_purchase_receipts() - else: - self.validate_applicable_charges_for_item() - self.validate_purchase_receipts() - self.set_total_taxes_and_charges() + + self.set_applicable_charges_on_item() + self.validate_applicable_charges_for_item() + def check_mandatory(self): if not self.get("purchase_receipts"): @@ -78,6 +81,33 @@ class LandedCostVoucher(Document): def set_total_taxes_and_charges(self): self.total_taxes_and_charges = sum([flt(d.base_amount) for d in self.get("taxes")]) + def set_applicable_charges_on_item(self): + if self.get('taxes'): + total_item_cost = 0.0 + total_charges = 0.0 + item_count = 0 + based_on_field = frappe.scrub(self.distribute_charges_based_on) + + for item in self.get('items'): + total_item_cost += item.get(based_on_field) + + for item in self.get('items'): + item.applicable_charges = flt(flt(item.get(based_on_field)) * flt(self.total_taxes_and_charges) / flt(total_item_cost), + item.precision('applicable_charges')) + total_charges += item.applicable_charges + item_count += 1 + + if total_charges != self.total_taxes_and_charges: + diff = self.total_taxes_and_charges - total_charges + self.get('items')[item_count - 1].applicable_charges += diff + + def set_account_currency(self): + company_currency = erpnext.get_company_currency(self.company) + for d in self.get('taxes'): + if not d.account_currency: + account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency') + d.account_currency = account_currency or company_currency + def set_exchange_rate(self): company_currency = erpnext.get_company_currency(self.company) for d in self.get('taxes'): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 3f2c5daf66..79d306b5db 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -10,6 +10,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ import set_perpetual_inventory, get_gl_entries, test_records as pr_test_records, make_purchase_receipt from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.account.test_account import get_inventory_account +from erpnext.accounts.doctype.account.test_account import create_account class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): @@ -27,7 +28,7 @@ class TestLandedCostVoucher(unittest.TestCase): }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount") self.assertEqual(pr_lc_value, 25.0) @@ -89,7 +90,7 @@ class TestLandedCostVoucher(unittest.TestCase): }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) - submit_landed_cost_voucher("Purchase Invoice", pi.name, pi.company) + create_landed_cost_voucher("Purchase Invoice", pi.name, pi.company) pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount") @@ -137,7 +138,7 @@ class TestLandedCostVoucher(unittest.TestCase): serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate") - submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1) @@ -160,7 +161,7 @@ class TestLandedCostVoucher(unittest.TestCase): }) pr.submit() - lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22) + lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22) self.assertEqual(lcv.items[0].applicable_charges, 41.07) self.assertEqual(lcv.items[2].applicable_charges, 41.08) @@ -206,6 +207,46 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertEqual(pr.items[0].landed_cost_voucher_amount, 100) self.assertEqual(pr.items[1].landed_cost_voucher_amount, 100) + def test_multi_currency_lcv(self): + ## Create USD Shipping charges_account + usd_shipping = create_account(account_name="Shipping Charges USD", + parent_account="Duties and Taxes - TCP1", company="_Test Company with perpetual inventory", + account_currency="USD") + + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", + supplier_warehouse = "Stores - TCP1") + pr.submit() + + lcv = make_landed_cost_voucher(company = pr.company, receipt_document_type = "Purchase Receipt", + receipt_document=pr.name, charges=100, do_not_save=True) + + lcv.append("taxes", { + "description": "Shipping Charges", + "expense_account": usd_shipping, + "amount": 10 + }) + + lcv.save() + lcv.submit() + pr.load_from_db() + + # Considering exchange rate from USD to INR as 62.9 + self.assertEqual(lcv.total_taxes_and_charges, 729) + self.assertEqual(pr.items[0].landed_cost_voucher_amount, 729) + + gl_entries = frappe.get_all("GL Entry", fields=["account", "credit", "credit_in_account_currency"], + filters={"voucher_no": pr.name, "account": ("in", ["Shipping Charges USD - TCP1", "Expenses Included In Valuation - TCP1"])}) + + expected_gl_entries = { + "Shipping Charges USD - TCP1": [629, 10], + "Expenses Included In Valuation - TCP1": [100, 100] + } + + for entry in gl_entries: + amounts = expected_gl_entries.get(entry.account) + self.assertEqual(entry.credit, amounts[0]) + self.assertEqual(entry.credit_in_account_currency, amounts[1]) + def make_landed_cost_voucher(** args): args = frappe._dict(args) ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) @@ -236,7 +277,7 @@ def make_landed_cost_voucher(** args): return lcv -def submit_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50): +def create_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50): ref_doc = frappe.get_doc(receipt_document_type, receipt_document) lcv = frappe.new_doc("Landed Cost Voucher") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 97e0fa738c..2cb8d2b26d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -279,12 +279,15 @@ class PurchaseReceipt(BuyingController): # Amount added through landed-cost-voucher if d.landed_cost_voucher_amount and landed_cost_entries: for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]): + account_currency = get_account_currency(account) gl_entries.append(self.get_gl_dict({ "account": account, + "account_currency": account_currency, "against": warehouse_account[d.warehouse]["account"], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(amount), + "credit": flt(amount["base_amount"]), + "credit_in_account_currency": flt(amount["amount"]), "project": d.project }, item=d)) @@ -729,9 +732,16 @@ def get_item_account_wise_additional_cost(purchase_document): if item.receipt_document == purchase_document: for account in landed_cost_voucher_doc.taxes: item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {}) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(account.expense_account, 0.0) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account] += \ + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(account.expense_account, { + "amount": 0.0, + "base_amount": 0.0 + }) + + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account]["amount"] += \ account.amount * item.get(based_on_field) / total_item_cost + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account]["base_amount"] += \ + account.base_amount * item.get(based_on_field) / total_item_cost + return item_account_wise_cost From d6cd02d29d26bc814328ded38f12c090a27a6970 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Dec 2020 09:21:24 +0530 Subject: [PATCH 03/11] fix: Add tests --- erpnext/accounts/doctype/account/test_account.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 0605d89a7e..fbcaa6cc18 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -254,7 +254,8 @@ def create_account(**kwargs): account_name = kwargs.get('account_name'), account_type = kwargs.get('account_type'), parent_account = kwargs.get('parent_account'), - company = kwargs.get('company') + company = kwargs.get('company'), + account_currency = kwargs.get('account_currency') )) account.save() From 6f99c0850c3e40eff4a0c4f43ba850a1bacda620 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Dec 2020 12:19:58 +0530 Subject: [PATCH 04/11] fix: Linting Issues --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.js | 2 +- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 82b5242704..c817f05e5a 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -174,7 +174,7 @@ frappe.ui.form.on('Landed Cost Voucher', { } } }); - }; + } }, set_base_amount: function(frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 519077afa4..ed54551595 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -193,8 +193,8 @@ class LandedCostVoucher(Document): docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document, 'item_code': item.item_code }, fields=['name', 'docstatus']) if not docs or len(docs) != item.qty: - frappe.throw(_('There are not enough asset created or linked to {0}. \ - Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty)) + frappe.throw(_('There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document.').format( + item.receipt_document, item.qty)) if docs: for d in docs: if d.docstatus == 1: From 51892efd60e8cd86fde4d9167eca064f691d1b4f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Dec 2020 12:35:22 +0530 Subject: [PATCH 05/11] fix: Translation issues --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index ed54551595..17bfa8fbb0 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -198,8 +198,7 @@ class LandedCostVoucher(Document): if docs: for d in docs: if d.docstatus == 1: - frappe.throw(_('{2} {0} has submitted Assets.\ - Remove Item {1} from table to continue.').format( + frappe.throw(_('{2} {0} has submitted Assets. Remove Item {1} from table to continue.').format( item.receipt_document, item.item_code, item.receipt_document_type)) def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): From e5e2ad646a762567115e0bb64e2d0e6acf7dd4c1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 22 Dec 2020 19:53:07 +0530 Subject: [PATCH 06/11] fix: Test Cases --- .../doctype/purchase_invoice/purchase_invoice.py | 11 ++++++----- .../landed_cost_voucher/landed_cost_voucher.py | 8 ++++---- .../landed_cost_voucher/test_landed_cost_voucher.py | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index b52678e8d3..3c29561ad1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -443,7 +443,7 @@ class PurchaseInvoice(BuyingController): else: self.stock_received_but_not_billed = None self.expenses_included_in_valuation = None - + self.negative_expense_to_be_booked = 0.0 gl_entries = [] @@ -457,7 +457,7 @@ class PurchaseInvoice(BuyingController): self.make_internal_transfer_gl_entries(gl_entries) gl_entries = make_regional_gl_entries(gl_entries, self) - + gl_entries = merge_similar_entries(gl_entries) self.make_payment_gl_entries(gl_entries) @@ -582,7 +582,8 @@ class PurchaseInvoice(BuyingController): "against": item.expense_account, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(amount), + "credit": flt(amount["base_amount"]), + "credit_in_account_currency": flt(amount["amount"]), "project": item.project or self.project }, item=item)) @@ -999,10 +1000,10 @@ class PurchaseInvoice(BuyingController): self.delete_auto_created_batches() self.make_gl_entries_on_cancel() - + if self.update_stock == 1: self.repost_future_sle_and_gle() - + self.update_project() frappe.db.set(self, 'status', 'Cancelled') diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 2d10e6a4ed..fee641e76c 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -92,14 +92,14 @@ class LandedCostVoucher(Document): total_item_cost += item.get(based_on_field) for item in self.get('items'): - item.applicable_charges = flt(flt(item.get(based_on_field)) * flt(self.total_taxes_and_charges) / flt(total_item_cost), + item.applicable_charges = flt(flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)), item.precision('applicable_charges')) total_charges += item.applicable_charges item_count += 1 - if total_charges != self.total_taxes_and_charges: - diff = self.total_taxes_and_charges - total_charges - self.get('items')[item_count - 1].applicable_charges += diff + if total_charges != self.total_taxes_and_charges: + diff = self.total_taxes_and_charges - total_charges + self.get('items')[item_count - 1].applicable_charges += diff def set_account_currency(self): company_currency = erpnext.get_company_currency(self.company) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index cd4b33ece1..144101c67d 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -163,8 +163,8 @@ class TestLandedCostVoucher(unittest.TestCase): lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22) - self.assertEqual(lcv.items[0].applicable_charges, 41.07) - self.assertEqual(lcv.items[2].applicable_charges, 41.08) + self.assertEqual(flt(lcv.items[0].applicable_charges, 2), 41.07) + self.assertEqual(flt(lcv.items[2].applicable_charges, 2), 41.08) def test_multiple_landed_cost_voucher_against_pr(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", From bfc17e487cce0cdcb2aaee25c0f0e233773629ad Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Dec 2020 18:34:39 +0530 Subject: [PATCH 07/11] fix: Commonify code for Stock Entry --- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/taxes_and_totals.py | 33 ++++++++++ .../landed_cost_voucher.js | 65 ++----------------- .../landed_cost_voucher.py | 30 +-------- .../stock/doctype/stock_entry/stock_entry.js | 29 +++++---- .../stock/doctype/stock_entry/stock_entry.py | 27 +++++--- .../stock_entry_detail.json | 2 +- .../stock/landed_taxes_and_charges_common.js | 58 +++++++++++++++++ 8 files changed, 136 insertions(+), 110 deletions(-) create mode 100644 erpnext/stock/landed_taxes_and_charges_common.js diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 49c9f8c9d0..6f68848754 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -442,7 +442,7 @@ class AccountsController(TransactionBase): account_currency = get_account_currency(gl_dict.account) if gl_dict.account and self.doctype not in ["Journal Entry", - "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice"]: + "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]: self.validate_account_currency(gl_dict.account, account_currency) set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 8dd2e5bacb..76309f8799 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -10,6 +10,7 @@ from erpnext.controllers.accounts_controller import validate_conversion_rate, \ validate_taxes_and_charges, validate_inclusive_tax from erpnext.stock.get_item_details import _get_item_tax_template from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules +from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate class calculate_taxes_and_totals(object): def __init__(self, doc): @@ -758,3 +759,35 @@ def get_rounded_tax_amount(itemised_tax, precision): for taxes in itemised_tax.values(): for tax_account in taxes: taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) + +class init_landed_taxes_and_totals(object): + def __init__(self, doc): + self.doc = doc + self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs' + self.set_account_currency() + self.set_exchange_rate() + self.set_amounts_in_company_currency() + + def set_account_currency(self): + company_currency = erpnext.get_company_currency(self.doc.company) + for d in self.doc.get(self.tax_field): + if not d.account_currency: + account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency') + d.account_currency = account_currency or company_currency + + def set_exchange_rate(self): + company_currency = erpnext.get_company_currency(self.doc.company) + for d in self.doc.get(self.tax_field): + if d.account_currency == company_currency: + d.exchange_rate = 1 + elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date: + d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account, + account_currency=d.account_currency, company=self.doc.company) + + if not d.exchange_rate: + frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx)) + + def set_amounts_in_company_currency(self): + for d in self.doc.get(self.tax_field): + d.amount = flt(d.amount, d.precision("amount")) + d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index c817f05e5a..02ff8fa858 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -1,6 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; frappe.provide("erpnext.stock"); @@ -29,19 +30,9 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ this.frm.add_fetch("receipt_document", "supplier", "supplier"); this.frm.add_fetch("receipt_document", "posting_date", "posting_date"); this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total"); - - this.frm.set_query("expense_account", "taxes", function() { - return { - filters: { - "account_type": ['in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]], - "company": me.frm.doc.company - } - }; - }); - }, - refresh: function(frm) { + refresh: function() { var help_content = `

@@ -71,6 +62,11 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
`; set_field_options("landed_cost_help", help_content); + + if (this.frm.doc.company) { + let company_currency = frappe.get_doc(":Company", this.frm.doc.company).default_currency; + this.frm.set_currency_labels(["total_taxes_and_charges"], company_currency); + } }, get_items_from_purchase_receipts: function() { @@ -137,53 +133,6 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ cur_frm.script_manager.make(erpnext.stock.LandedCostVoucher); -frappe.ui.form.on('Landed Cost Voucher', { - set_account_currency: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.expense_account) { - frappe.db.get_value('Account', row.expense_account, 'account_currency', function(value) { - frappe.model.set_value(cdt, cdn, "account_currency", value.account_currency); - frm.events.set_exchange_rate(frm, cdt, cdn); - }); - } - }, - - onload: function(frm) { - let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - frm.set_currency_labels(["total_taxes_and_charges"], company_currency); - }, - - set_exchange_rate: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - - if (row.account_currency == company_currency) { - row.exchange_rate = 1; - } else if (!row.exchange_rate || row.exchange_rate == 1) { - frappe.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", - args: { - posting_date: frm.doc.posting_date, - account: row.expense_account, - account_currency: row.account_currency, - company: frm.doc.company - }, - callback: function(r) { - if (r.message) { - frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); - } - } - }); - } - }, - - set_base_amount: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, "base_amount", - flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row))); - } -}); - frappe.ui.form.on('Landed Cost Taxes and Charges', { expense_account: function(frm, cdt, cdn) { frm.events.set_account_currency(frm, cdt, cdn); diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index fee641e76c..24bbc4046b 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -9,7 +9,7 @@ from frappe.model.meta import get_field_precision from frappe.model.document import Document from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.accounts.doctype.account.account import get_account_currency -from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate +from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals class LandedCostVoucher(Document): def get_items_from_purchase_receipts(self): @@ -41,9 +41,7 @@ class LandedCostVoucher(Document): def validate(self): self.check_mandatory() self.validate_purchase_receipts() - self.set_account_currency() - self.set_exchange_rate() - self.set_amounts_in_company_currency() + init_landed_taxes_and_totals(self) self.set_total_taxes_and_charges() if not self.get("items"): self.get_items_from_purchase_receipts() @@ -101,30 +99,6 @@ class LandedCostVoucher(Document): diff = self.total_taxes_and_charges - total_charges self.get('items')[item_count - 1].applicable_charges += diff - def set_account_currency(self): - company_currency = erpnext.get_company_currency(self.company) - for d in self.get('taxes'): - if not d.account_currency: - account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency') - d.account_currency = account_currency or company_currency - - def set_exchange_rate(self): - company_currency = erpnext.get_company_currency(self.company) - for d in self.get('taxes'): - if d.account_currency == company_currency: - d.exchange_rate = 1 - elif not d.exchange_rate or d.exchange_rate == 1 or self.posting_date: - d.exchange_rate = get_exchange_rate(self.posting_date, account=d.expense_account, - account_currency=d.account_currency, company=self.company) - - if not d.exchange_rate: - frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx)) - - def set_amounts_in_company_currency(self): - for d in self.get('taxes'): - d.amount = flt(d.amount, d.precision("amount")) - d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) - def validate_applicable_charges_for_item(self): based_on = self.distribute_charges_based_on.lower() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 98116ec183..f3f5ac8b46 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1,6 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.stock"); +{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; + frappe.ui.form.on('Stock Entry', { setup: function(frm) { @@ -86,15 +88,6 @@ frappe.ui.form.on('Stock Entry', { } }); - frm.set_query("expense_account", "additional_costs", function() { - return { - query: "erpnext.controllers.queries.tax_account_query", - filters: { - "account_type": ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"], - "company": frm.doc.company - } - }; - }); frm.add_fetch("bom_no", "inspection_required", "inspection_required"); }, @@ -524,7 +517,7 @@ frappe.ui.form.on('Stock Entry', { }) ); } - + for (let i in frm.doc.items) { let item = frm.doc.items[i]; @@ -547,7 +540,7 @@ frappe.ui.form.on('Stock Entry', { calculate_total_additional_costs: function(frm) { const total_additional_costs = frappe.utils.sum( - (frm.doc.additional_costs || []).map(function(c) { return flt(c.amount); }) + (frm.doc.additional_costs || []).map(function(c) { return flt(c.base_amount); }) ); frm.set_value("total_additional_costs", @@ -716,8 +709,18 @@ var validate_sample_quantity = function(frm, cdt, cdn) { }; frappe.ui.form.on('Landed Cost Taxes and Charges', { - amount: function(frm) { - frm.events.calculate_amount(frm); + amount: function(frm, cdt, cdn) { + frm.events.set_base_amount(frm, cdt, cdn); + + // Adding this check because same table in used in LCV + // This causes an error if you try to post an LCV immediately after a Stock Entry + if (frm.doc.doctype == 'Stock Entry') { + frm.events.calculate_amount(frm); + } + }, + + expense_account: function(frm, cdt, cdn) { + frm.events.set_account_currency(frm, cdt, cdn); } }); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index afdb54ceaa..adaaff7b81 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -19,6 +19,7 @@ from frappe.model.mapper import get_mapped_doc from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError from erpnext.accounts.general_ledger import process_gl_map +from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals import json from six import string_types, itervalues, iteritems @@ -186,7 +187,7 @@ class StockEntry(StockController): and (sed.t_warehouse is null or sed.t_warehouse = '')""", self.project, as_list=1) amount = amount[0][0] if amount else 0 - additional_costs = frappe.db.sql(""" select ifnull(sum(sed.amount), 0) + additional_costs = frappe.db.sql(""" select ifnull(sum(sed.base_amount), 0) from `tabStock Entry` se, `tabLanded Cost Taxes and Charges` sed where @@ -431,6 +432,7 @@ class StockEntry(StockController): def calculate_rate_and_amount(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): self.set_basic_rate(reset_outgoing_rate, raise_error_if_no_rate) + init_landed_taxes_and_totals(self) self.distribute_additional_costs() self.update_valuation_rate() self.set_total_incoming_outgoing_value() @@ -518,7 +520,7 @@ class StockEntry(StockController): if not any([d.item_code for d in self.items if d.t_warehouse]): self.additional_costs = [] - self.total_additional_costs = sum([flt(t.amount) for t in self.get("additional_costs")]) + self.total_additional_costs = sum([flt(t.base_amount) for t in self.get("additional_costs")]) if self.purpose in ("Repack", "Manufacture"): incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item]) @@ -698,7 +700,7 @@ class StockEntry(StockController): # SLE for target warehouse self.get_sle_for_target_warehouse(sl_entries, finished_item_row) - + # reverse sl entries if cancel if self.docstatus == 2: sl_entries.reverse() @@ -726,9 +728,9 @@ class StockEntry(StockController): sle.dependant_sle_voucher_detail_no = d.name elif finished_item_row and (finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse): sle.dependant_sle_voucher_detail_no = finished_item_row.name - + sl_entries.append(sle) - + def get_sle_for_target_warehouse(self, sl_entries, finished_item_row): for d in self.get('items'): if cstr(d.t_warehouse): @@ -758,13 +760,19 @@ class StockEntry(StockController): for d in self.get("items"): if d.t_warehouse: item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) - item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0) + item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { + "amount": 0.0, + "base_amount": 0.0 + }) multiply_based_on = d.basic_amount if total_basic_amount else d.qty - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ flt(t.amount * multiply_based_on) / divide_based_on + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ + flt(t.base_amount * multiply_based_on) / divide_based_on + if item_account_wise_additional_cost: for d in self.get("items"): for account, amount in iteritems(item_account_wise_additional_cost.get((d.item_code, d.name), {})): @@ -775,7 +783,8 @@ class StockEntry(StockController): "against": d.expense_account, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": amount + "credit_in_account_currency": flt(amount["amount"]), + "credit": flt(amount["base_amount"]) }, item=d)) gl_entries.append(self.get_gl_dict({ @@ -783,7 +792,7 @@ class StockEntry(StockController): "against": account, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": -1 * amount # put it as negative credit instead of debit purposefully + "credit": -1 * amount['base_amount'] # put it as negative credit instead of debit purposefully }, item=d)) return process_gl_map(gl_entries) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 6fe60298ee..b4c2284273 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -526,7 +526,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-23 17:55:03.384138", + "modified": "2020-09-25 17:55:03.384138", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js new file mode 100644 index 0000000000..8ad08808bc --- /dev/null +++ b/erpnext/stock/landed_taxes_and_charges_common.js @@ -0,0 +1,58 @@ +let document_list = ['Landed Cost Voucher', 'Stock Entry']; + +document_list.forEach((doctype) => { + frappe.ui.form.on(doctype, { + refresh: function(frm) { + let tax_field = frm.doc.doctype == 'Landed Cost Voucher' ? 'taxes' : 'additional_costs'; + frm.set_query("expense_account", tax_field, function() { + return { + filters: { + "account_type": ['in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]], + "company": frm.doc.company + } + }; + }); + }, + + set_account_currency: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.expense_account) { + frappe.db.get_value('Account', row.expense_account, 'account_currency', function(value) { + frappe.model.set_value(cdt, cdn, "account_currency", value.account_currency); + frm.events.set_exchange_rate(frm, cdt, cdn); + }); + } + }, + + set_exchange_rate: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + + if (row.account_currency == company_currency) { + row.exchange_rate = 1; + } else if (!row.exchange_rate || row.exchange_rate == 1) { + frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", + args: { + posting_date: frm.doc.posting_date, + account: row.expense_account, + account_currency: row.account_currency, + company: frm.doc.company + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "exchange_rate", r.message); + } + } + }); + } + }, + + set_base_amount: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, "base_amount", + flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row))); + } + }); +}); + From 4f8da4c31312d55036c19a160bec069bd3ffd8cd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 26 Dec 2020 11:37:51 +0530 Subject: [PATCH 08/11] fix: Test Cases --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 2 +- .../landed_cost_taxes_and_charges.json | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index c441274908..fc849647d4 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -149,7 +149,7 @@ class GLEntry(Document): account_currency = get_account_currency(self.account) if not self.account_currency: - self.account_currency = company_currency + self.account_currency = account_currency or company_currency if account_currency != self.account_currency: frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}") diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 0861224de4..4fcdb4c10c 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -41,16 +41,14 @@ "in_list_view": 1, "label": "Expense Account", "mandatory_depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))", - "options": "Account", - "reqd": 1 + "options": "Account" }, { "fieldname": "account_currency", "fieldtype": "Link", "label": "Account Currency", "options": "Currency", - "read_only": 1, - "reqd": 1 + "read_only": 1 }, { "fieldname": "exchange_rate", @@ -63,14 +61,13 @@ "fieldtype": "Currency", "label": "Base Amount", "options": "Company:company:default_currency", - "read_only": 1, - "reqd": 1 + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-04 00:22:14.373312", + "modified": "2020-12-26 01:07:23.233604", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Taxes and Charges", From 7c300859ed11ae860adcb90b8bde403097c289f6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 26 Dec 2020 20:14:51 +0530 Subject: [PATCH 09/11] fix: Test Cases --- erpnext/controllers/accounts_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cfb2949e7f..7ac4cfeef7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -110,12 +110,12 @@ class AccountsController(TransactionBase): self.set_inter_company_account() validate_regional(self) - + validate_einvoice_fields(self) if self.doctype != 'Material Request': apply_pricing_rule_on_transaction(self) - + def before_cancel(self): validate_einvoice_fields(self) @@ -450,6 +450,8 @@ class AccountsController(TransactionBase): if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]: self.validate_account_currency(gl_dict.account, account_currency) + + if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]: set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency) From a0d61524c186b07c0fd0ccaf4423087b385f717c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 26 Jan 2021 17:05:00 +0530 Subject: [PATCH 10/11] fix: Add distribute manually option in LCV --- .../landed_cost_item/landed_cost_item.json | 57 ++++++++++---- .../landed_cost_voucher.js | 30 ++++---- .../landed_cost_voucher.json | 76 ++++++++++++++----- .../landed_cost_voucher.py | 8 +- .../purchase_receipt/purchase_receipt.py | 8 +- .../stock/landed_taxes_and_charges_common.js | 4 + 6 files changed, 133 insertions(+), 50 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json index b24d621c31..c77b993167 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2013-02-22 01:28:02", "doctype": "DocType", "document_type": "Document", @@ -29,6 +30,8 @@ "options": "Item", "read_only": 1, "reqd": 1, + "show_days": 1, + "show_seconds": 1, "width": "100px" }, { @@ -41,6 +44,8 @@ "print_width": "300px", "read_only": 1, "reqd": 1, + "show_days": 1, + "show_seconds": 1, "width": "120px" }, { @@ -50,7 +55,9 @@ "no_copy": 1, "options": "Purchase Invoice\nPurchase Receipt", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "receipt_document", @@ -59,25 +66,33 @@ "no_copy": 1, "options": "receipt_document_type", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, "label": "Qty", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rate", "fieldtype": "Currency", "label": "Rate", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amount", @@ -88,14 +103,19 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "applicable_charges", "fieldtype": "Currency", "in_list_view": 1, "label": "Applicable Charges", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only_depends_on": "eval:parent.distribute_charges_based_on != 'Distribute Manually'", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "purchase_receipt_item", @@ -104,22 +124,30 @@ "label": "Purchase Receipt Item", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions" + "label": "Accounting Dimensions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -128,12 +156,15 @@ "fieldtype": "Check", "hidden": 1, "label": "Is Fixed Asset", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], "idx": 1, "istable": 1, - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2021-01-25 23:09:23.322282", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Item", diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 02ff8fa858..1abbc35334 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -101,25 +101,27 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ var me = this; if(this.frm.doc.taxes.length) { - var total_item_cost = 0.0; var based_on = this.frm.doc.distribute_charges_based_on.toLowerCase(); - $.each(this.frm.doc.items || [], function(i, d) { - total_item_cost += flt(d[based_on]) - }); - var total_charges = 0.0; - $.each(this.frm.doc.items || [], function(i, item) { - item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost) - item.applicable_charges = flt(item.applicable_charges, precision("applicable_charges", item)) - total_charges += item.applicable_charges - }); + if (based_on != 'distribute manually') { + $.each(this.frm.doc.items || [], function(i, d) { + total_item_cost += flt(d[based_on]) + }); - if (total_charges != this.frm.doc.total_taxes_and_charges){ - var diff = this.frm.doc.total_taxes_and_charges - flt(total_charges) - this.frm.doc.items.slice(-1)[0].applicable_charges += diff + var total_charges = 0.0; + $.each(this.frm.doc.items || [], function(i, item) { + item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost) + item.applicable_charges = flt(item.applicable_charges, precision("applicable_charges", item)) + total_charges += item.applicable_charges + }); + + if (total_charges != this.frm.doc.total_taxes_and_charges){ + var diff = this.frm.doc.total_taxes_and_charges - flt(total_charges) + this.frm.doc.items.slice(-1)[0].applicable_charges += diff + } + refresh_field("items"); } - refresh_field("items"); } }, distribute_charges_based_on: function (frm) { diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json index 6a7994ba9e..059f925184 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -34,7 +34,9 @@ "options": "MAT-LCV-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -44,24 +46,32 @@ "label": "Company", "options": "Company", "remember_last_selected_value": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "purchase_receipts", "fieldtype": "Table", "label": "Purchase Receipts", "options": "Landed Cost Purchase Receipt", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "purchase_receipt_items", "fieldtype": "Section Break", - "label": "Purchase Receipt Items" + "label": "Purchase Receipt Items", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "get_items_from_purchase_receipts", "fieldtype": "Button", - "label": "Get Items From Purchase Receipts" + "label": "Get Items From Purchase Receipts", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "items", @@ -69,23 +79,31 @@ "label": "Purchase Receipt Items", "no_copy": 1, "options": "Landed Cost Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_break1", "fieldtype": "Section Break", - "label": "Applicable Charges" + "label": "Applicable Charges", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes", "fieldtype": "Table", "label": "Taxes and Charges", "options": "Landed Cost Taxes and Charges", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_9", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_taxes_and_charges", @@ -93,18 +111,24 @@ "label": "Total Taxes and Charges (Company Currency)", "options": "Company:company:default_currency", "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break1", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "distribute_charges_based_on", "fieldtype": "Select", "label": "Distribute Charges Based On", - "options": "Qty\nAmount", - "reqd": 1 + "options": "Qty\nAmount\nDistribute Manually", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -113,39 +137,51 @@ "no_copy": 1, "options": "Landed Cost Voucher", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_break2", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "landed_cost_help", "fieldtype": "HTML", - "label": "Landed Cost Help" + "label": "Landed Cost Help", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Today", "fieldname": "posting_date", "fieldtype": "Date", "label": "Posting Date", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_5", "fieldtype": "Section Break", - "hide_border": 1 + "hide_border": 1, + "show_days": 1, + "show_seconds": 1 } ], "icon": "icon-usd", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-13 23:18:47.442466", + "modified": "2021-01-25 23:07:30.468423", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Voucher", diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 24bbc4046b..69a8bf19d3 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -80,7 +80,7 @@ class LandedCostVoucher(Document): self.total_taxes_and_charges = sum([flt(d.base_amount) for d in self.get("taxes")]) def set_applicable_charges_on_item(self): - if self.get('taxes'): + if self.get('taxes') and self.distribute_charges_based_on != 'Distribute Manually': total_item_cost = 0.0 total_charges = 0.0 item_count = 0 @@ -102,7 +102,11 @@ class LandedCostVoucher(Document): def validate_applicable_charges_for_item(self): based_on = self.distribute_charges_based_on.lower() - total = sum([flt(d.get(based_on)) for d in self.get("items")]) + if based_on != 'distribute manually': + total = sum([flt(d.get(based_on)) for d in self.get("items")]) + else: + # consider for proportion while distributing manually + total = sum([flt(d.get('applicable_charges')) for d in self.get("items")]) if not total: frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on)) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index fd9f5b851e..550c849c5d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -731,7 +731,13 @@ def get_item_account_wise_additional_cost(purchase_document): for lcv in landed_cost_vouchers: landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent) - based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) + + #Use amount field for total item cost for manually cost distributed LCVs + if landed_cost_voucher_doc.distribute_charges_based_on == 'Distribute Manually': + based_on_field = 'amount' + else: + based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) + total_item_cost = 0 for item in landed_cost_voucher_doc.items: diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js index 8ad08808bc..f3f61963a8 100644 --- a/erpnext/stock/landed_taxes_and_charges_common.js +++ b/erpnext/stock/landed_taxes_and_charges_common.js @@ -30,7 +30,9 @@ document_list.forEach((doctype) => { if (row.account_currency == company_currency) { row.exchange_rate = 1; + frm.set_df_property('taxes', 'hidden', 1, row.name, 'exchange_rate'); } else if (!row.exchange_rate || row.exchange_rate == 1) { + frm.set_df_property('taxes', 'hidden', 0, row.name, 'exchange_rate'); frappe.call({ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", args: { @@ -46,6 +48,8 @@ document_list.forEach((doctype) => { } }); } + + frm.refresh_field('taxes'); }, set_base_amount: function(frm, cdt, cdn) { From dade7a45839beb8995c3f0390ad09c8f165bedc7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 26 Jan 2021 21:11:55 +0530 Subject: [PATCH 11/11] fix: Translation Syntax --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 1dd235dc64..b0a864f76c 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -159,8 +159,8 @@ class GLEntry(Document): if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \ and self.cost_center and _check_is_group(): - frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot - be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) + frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format( + self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) def validate_party(self): validate_party_frozen_disabled(self.party_type, self.party)