Merge pull request #24127 from deepeshgarg007/lcv_multicurrency

feat: Multi currency in landed cost voucher
This commit is contained in:
Marica 2021-01-28 13:35:37 +05:30 committed by GitHub
commit a16ad3063f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 418 additions and 113 deletions

View File

@ -254,7 +254,8 @@ def create_account(**kwargs):
account_name = kwargs.get('account_name'), account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'), account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'), parent_account = kwargs.get('parent_account'),
company = kwargs.get('company') company = kwargs.get('company'),
account_currency = kwargs.get('account_currency')
)) ))
account.save() account.save()

View File

@ -159,8 +159,8 @@ class GLEntry(Document):
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \ if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
and self.cost_center and _check_is_group(): 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 frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self): def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party) validate_party_frozen_disabled(self.party_type, self.party)
@ -170,7 +170,7 @@ class GLEntry(Document):
account_currency = get_account_currency(self.account) account_currency = get_account_currency(self.account)
if not self.account_currency: if not self.account_currency:
self.account_currency = company_currency self.account_currency = account_currency or company_currency
if account_currency != self.account_currency: if account_currency != self.account_currency:
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}") frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")

View File

@ -583,7 +583,8 @@ class PurchaseInvoice(BuyingController):
"against": item.expense_account, "against": item.expense_account,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "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 "project": item.project or self.project
}, item=item)) }, item=item))

View File

@ -468,8 +468,10 @@ class AccountsController(TransactionBase):
account_currency = get_account_currency(gl_dict.account) account_currency = get_account_currency(gl_dict.account)
if gl_dict.account and self.doctype not in ["Journal Entry", 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", "Stock Entry"]:
self.validate_account_currency(gl_dict.account, account_currency) 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"), set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
self.company_currency) self.company_currency)

View File

@ -10,6 +10,7 @@ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax validate_taxes_and_charges, validate_inclusive_tax
from erpnext.stock.get_item_details import _get_item_tax_template 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.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): class calculate_taxes_and_totals(object):
def __init__(self, doc): def __init__(self, doc):
@ -758,3 +759,35 @@ def get_rounded_tax_amount(itemised_tax, precision):
for taxes in itemised_tax.values(): for taxes in itemised_tax.values():
for tax_account in taxes: for tax_account in taxes:
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) 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"))

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2013-02-22 01:28:02", "creation": "2013-02-22 01:28:02",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Document", "document_type": "Document",
@ -29,6 +30,8 @@
"options": "Item", "options": "Item",
"read_only": 1, "read_only": 1,
"reqd": 1, "reqd": 1,
"show_days": 1,
"show_seconds": 1,
"width": "100px" "width": "100px"
}, },
{ {
@ -41,6 +44,8 @@
"print_width": "300px", "print_width": "300px",
"read_only": 1, "read_only": 1,
"reqd": 1, "reqd": 1,
"show_days": 1,
"show_seconds": 1,
"width": "120px" "width": "120px"
}, },
{ {
@ -50,7 +55,9 @@
"no_copy": 1, "no_copy": 1,
"options": "Purchase Invoice\nPurchase Receipt", "options": "Purchase Invoice\nPurchase Receipt",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "receipt_document", "fieldname": "receipt_document",
@ -59,25 +66,33 @@
"no_copy": 1, "no_copy": 1,
"options": "receipt_document_type", "options": "receipt_document_type",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "col_break2", "fieldname": "col_break2",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "qty", "fieldname": "qty",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Qty", "label": "Qty",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Rate", "label": "Rate",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "amount", "fieldname": "amount",
@ -88,14 +103,19 @@
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1, "read_only": 1,
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "applicable_charges", "fieldname": "applicable_charges",
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Applicable Charges", "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", "fieldname": "purchase_receipt_item",
@ -104,22 +124,30 @@
"label": "Purchase Receipt Item", "label": "Purchase Receipt Item",
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Cost Center", "label": "Cost Center",
"options": "Cost Center" "options": "Cost Center",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Accounting Dimensions" "label": "Accounting Dimensions",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
@ -128,12 +156,15 @@
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
"label": "Is Fixed Asset", "label": "Is Fixed Asset",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2020-09-18 17:26:09.703215", "links": [],
"modified": "2021-01-25 23:09:23.322282",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Landed Cost Item", "name": "Landed Cost Item",

View File

@ -6,8 +6,11 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"expense_account", "expense_account",
"account_currency",
"exchange_rate",
"description", "description",
"col_break3", "col_break3",
"base_amount",
"amount" "amount"
], ],
"fields": [ "fields": [
@ -28,7 +31,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Amount", "label": "Amount",
"options": "Company:company:default_currency", "options": "account_currency",
"reqd": 1 "reqd": 1
}, },
{ {
@ -38,13 +41,33 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Expense Account", "label": "Expense Account",
"mandatory_depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))", "mandatory_depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))",
"options": "Account", "options": "Account"
"print_hide": 1 },
{
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 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
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-12-04 00:22:14.373312", "modified": "2020-12-26 01:07:23.233604",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Landed Cost Taxes and Charges", "name": "Landed Cost Taxes and Charges",

View File

@ -1,6 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
frappe.provide("erpnext.stock"); frappe.provide("erpnext.stock");
@ -29,20 +30,9 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
this.frm.add_fetch("receipt_document", "supplier", "supplier"); this.frm.add_fetch("receipt_document", "supplier", "supplier");
this.frm.add_fetch("receipt_document", "posting_date", "posting_date"); this.frm.add_fetch("receipt_document", "posting_date", "posting_date");
this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total"); this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total");
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"],
"company": me.frm.doc.company
}
};
});
}, },
refresh: function(frm) { refresh: function() {
var help_content = var help_content =
`<br><br> `<br><br>
<table class="table table-bordered" style="background-color: #f9f9f9;"> <table class="table table-bordered" style="background-color: #f9f9f9;">
@ -72,6 +62,11 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
</table>`; </table>`;
set_field_options("landed_cost_help", help_content); 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() { get_items_from_purchase_receipts: function() {
@ -97,18 +92,19 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
set_total_taxes_and_charges: function() { set_total_taxes_and_charges: function() {
var total_taxes_and_charges = 0.0; var total_taxes_and_charges = 0.0;
$.each(this.frm.doc.taxes || [], function(i, d) { $.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); this.frm.set_value("total_taxes_and_charges", total_taxes_and_charges);
}, },
set_applicable_charges_for_item: function() { set_applicable_charges_for_item: function() {
var me = this; var me = this;
if(this.frm.doc.taxes.length) { if(this.frm.doc.taxes.length) {
var total_item_cost = 0.0; var total_item_cost = 0.0;
var based_on = this.frm.doc.distribute_charges_based_on.toLowerCase(); var based_on = this.frm.doc.distribute_charges_based_on.toLowerCase();
if (based_on != 'distribute manually') {
$.each(this.frm.doc.items || [], function(i, d) { $.each(this.frm.doc.items || [], function(i, d) {
total_item_cost += flt(d[based_on]) total_item_cost += flt(d[based_on])
}); });
@ -126,6 +122,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
} }
refresh_field("items"); refresh_field("items");
} }
}
}, },
distribute_charges_based_on: function (frm) { distribute_charges_based_on: function (frm) {
this.set_applicable_charges_for_item(); this.set_applicable_charges_for_item();
@ -134,7 +131,16 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
items_remove: () => { items_remove: () => {
this.trigger('set_applicable_charges_for_item'); this.trigger('set_applicable_charges_for_item');
} }
}); });
cur_frm.script_manager.make(erpnext.stock.LandedCostVoucher); cur_frm.script_manager.make(erpnext.stock.LandedCostVoucher);
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);
}
});

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2014-07-11 11:33:42.547339", "creation": "2014-07-11 11:33:42.547339",
"doctype": "DocType", "doctype": "DocType",
@ -7,6 +8,9 @@
"field_order": [ "field_order": [
"naming_series", "naming_series",
"company", "company",
"column_break_2",
"posting_date",
"section_break_5",
"purchase_receipts", "purchase_receipts",
"purchase_receipt_items", "purchase_receipt_items",
"get_items_from_purchase_receipts", "get_items_from_purchase_receipts",
@ -30,7 +34,9 @@
"options": "MAT-LCV-.YYYY.-", "options": "MAT-LCV-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
"set_only_once": 1 "set_only_once": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
@ -40,24 +46,32 @@
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "purchase_receipts", "fieldname": "purchase_receipts",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Purchase Receipts", "label": "Purchase Receipts",
"options": "Landed Cost Purchase Receipt", "options": "Landed Cost Purchase Receipt",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "purchase_receipt_items", "fieldname": "purchase_receipt_items",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Purchase Receipt Items" "label": "Purchase Receipt Items",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "get_items_from_purchase_receipts", "fieldname": "get_items_from_purchase_receipts",
"fieldtype": "Button", "fieldtype": "Button",
"label": "Get Items From Purchase Receipts" "label": "Get Items From Purchase Receipts",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "items", "fieldname": "items",
@ -65,42 +79,56 @@
"label": "Purchase Receipt Items", "label": "Purchase Receipt Items",
"no_copy": 1, "no_copy": 1,
"options": "Landed Cost Item", "options": "Landed Cost Item",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "sec_break1", "fieldname": "sec_break1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Applicable Charges" "label": "Applicable Charges",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "taxes", "fieldname": "taxes",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Taxes and Charges", "label": "Taxes and Charges",
"options": "Landed Cost Taxes and Charges", "options": "Landed Cost Taxes and Charges",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "section_break_9", "fieldname": "section_break_9",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "total_taxes_and_charges", "fieldname": "total_taxes_and_charges",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Total Taxes and Charges", "label": "Total Taxes and Charges (Company Currency)",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1, "read_only": 1,
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "col_break1", "fieldname": "col_break1",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "distribute_charges_based_on", "fieldname": "distribute_charges_based_on",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Distribute Charges Based On", "label": "Distribute Charges Based On",
"options": "Qty\nAmount", "options": "Qty\nAmount\nDistribute Manually",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
@ -109,21 +137,51 @@
"no_copy": 1, "no_copy": 1,
"options": "Landed Cost Voucher", "options": "Landed Cost Voucher",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "sec_break2", "fieldname": "sec_break2",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "landed_cost_help", "fieldname": "landed_cost_help",
"fieldtype": "HTML", "fieldtype": "HTML",
"label": "Landed Cost Help" "label": "Landed Cost Help",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hide_border": 1,
"show_days": 1,
"show_seconds": 1
} }
], ],
"icon": "icon-usd", "icon": "icon-usd",
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-11-21 15:34:10.846093", "links": [],
"modified": "2021-01-25 23:07:30.468423",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Landed Cost Voucher", "name": "Landed Cost Voucher",

View File

@ -9,6 +9,7 @@ from frappe.model.meta import get_field_precision
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos 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.account.account import get_account_currency
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
class LandedCostVoucher(Document): class LandedCostVoucher(Document):
def get_items_from_purchase_receipts(self): def get_items_from_purchase_receipts(self):
@ -39,13 +40,15 @@ class LandedCostVoucher(Document):
def validate(self): def validate(self):
self.check_mandatory() self.check_mandatory()
self.validate_purchase_receipts()
init_landed_taxes_and_totals(self)
self.set_total_taxes_and_charges()
if not self.get("items"): if not self.get("items"):
self.get_items_from_purchase_receipts() self.get_items_from_purchase_receipts()
else:
self.set_applicable_charges_on_item()
self.validate_applicable_charges_for_item() self.validate_applicable_charges_for_item()
self.validate_purchase_receipts()
self.validate_expense_accounts()
self.set_total_taxes_and_charges()
def check_mandatory(self): def check_mandatory(self):
if not self.get("purchase_receipts"): if not self.get("purchase_receipts"):
@ -73,21 +76,37 @@ class LandedCostVoucher(Document):
frappe.throw(_("Row {0}: Cost center is required for an item {1}") frappe.throw(_("Row {0}: Cost center is required for an item {1}")
.format(item.idx, item.item_code)) .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(_("Row {}: Expense account currency should be same as company's default currency.").format(account.idx)
+ _("Please select expense account with account currency as {}.").format(frappe.bold(company_currency)),
title=_("Invalid Account Currency"))
def set_total_taxes_and_charges(self): 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_applicable_charges_on_item(self):
if self.get('taxes') and self.distribute_charges_based_on != 'Distribute Manually':
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 validate_applicable_charges_for_item(self): def validate_applicable_charges_for_item(self):
based_on = self.distribute_charges_based_on.lower() based_on = self.distribute_charges_based_on.lower()
if based_on != 'distribute manually':
total = sum([flt(d.get(based_on)) for d in self.get("items")]) 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: if not total:
frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on)) frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on))
@ -153,13 +172,13 @@ class LandedCostVoucher(Document):
docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document, docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document,
'item_code': item.item_code }, fields=['name', 'docstatus']) 'item_code': item.item_code }, fields=['name', 'docstatus'])
if not docs or len(docs) != item.qty: if not docs or len(docs) != item.qty:
frappe.throw(_('There are not enough asset created or linked to {0}.').format(item.receipt_document) frappe.throw(_('There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document.').format(
+ _('Please create or link {0} Assets with respective document.').format(item.qty)) item.receipt_document, item.qty))
if docs: if docs:
for d in docs: for d in docs:
if d.docstatus == 1: if d.docstatus == 1:
frappe.throw(_('{0} {1} has submitted Assets. Remove Item {2} from table to continue.') frappe.throw(_('{2} <b>{0}</b> has submitted Assets. Remove Item <b>{1}</b> from table to continue.').format(
.format(item.receipt_document_type, frappe.bold(item.receipt_document), frappe.bold(item.item_code))) item.receipt_document, item.item_code, item.receipt_document_type))
def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
for item in receipt_document.get("items"): for item in receipt_document.get("items"):

View File

@ -10,6 +10,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
import get_gl_entries, test_records as pr_test_records, make_purchase_receipt import 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.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 get_inventory_account
from erpnext.accounts.doctype.account.test_account import create_account
class TestLandedCostVoucher(unittest.TestCase): class TestLandedCostVoucher(unittest.TestCase):
def test_landed_cost_voucher(self): def test_landed_cost_voucher(self):
@ -162,8 +163,8 @@ class TestLandedCostVoucher(unittest.TestCase):
lcv = create_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(flt(lcv.items[0].applicable_charges, 2), 41.07)
self.assertEqual(lcv.items[2].applicable_charges, 41.08) self.assertEqual(flt(lcv.items[2].applicable_charges, 2), 41.08)
def test_multiple_landed_cost_voucher_against_pr(self): def test_multiple_landed_cost_voucher_against_pr(self):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
@ -206,6 +207,46 @@ class TestLandedCostVoucher(unittest.TestCase):
self.assertEqual(pr.items[0].landed_cost_voucher_amount, 100) self.assertEqual(pr.items[0].landed_cost_voucher_amount, 100)
self.assertEqual(pr.items[1].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): def make_landed_cost_voucher(** args):
args = frappe._dict(args) args = frappe._dict(args)
ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document)

View File

@ -288,12 +288,15 @@ class PurchaseReceipt(BuyingController):
# Amount added through landed-cost-voucher # Amount added through landed-cost-voucher
if d.landed_cost_voucher_amount and landed_cost_entries: if d.landed_cost_voucher_amount and landed_cost_entries:
for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]): 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({ gl_entries.append(self.get_gl_dict({
"account": account, "account": account,
"account_currency": account_currency,
"against": warehouse_account[d.warehouse]["account"], "against": warehouse_account[d.warehouse]["account"],
"cost_center": d.cost_center, "cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "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 "project": d.project
}, item=d)) }, item=d))
@ -728,7 +731,13 @@ def get_item_account_wise_additional_cost(purchase_document):
for lcv in landed_cost_vouchers: for lcv in landed_cost_vouchers:
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent) landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
#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) based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
total_item_cost = 0 total_item_cost = 0
for item in landed_cost_voucher_doc.items: for item in landed_cost_voucher_doc.items:
@ -738,9 +747,16 @@ def get_item_account_wise_additional_cost(purchase_document):
if item.receipt_document == purchase_document: if item.receipt_document == purchase_document:
for account in landed_cost_voucher_doc.taxes: for account in landed_cost_voucher_doc.taxes:
item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {}) 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)].setdefault(account.expense_account, {
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][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 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 return item_account_wise_cost

View File

@ -3,6 +3,8 @@
frappe.provide("erpnext.stock"); frappe.provide("erpnext.stock");
frappe.provide("erpnext.accounts.dimensions"); frappe.provide("erpnext.accounts.dimensions");
{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
frappe.ui.form.on('Stock Entry', { frappe.ui.form.on('Stock Entry', {
setup: function(frm) { setup: function(frm) {
frm.set_indicator_formatter('item_code', function(doc) { frm.set_indicator_formatter('item_code', function(doc) {
@ -95,15 +97,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"); frm.add_fetch("bom_no", "inspection_required", "inspection_required");
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
@ -559,7 +552,7 @@ frappe.ui.form.on('Stock Entry', {
calculate_total_additional_costs: function(frm) { calculate_total_additional_costs: function(frm) {
const total_additional_costs = frappe.utils.sum( 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", frm.set_value("total_additional_costs",
@ -738,9 +731,19 @@ var validate_sample_quantity = function(frm, cdt, cdn) {
}; };
frappe.ui.form.on('Landed Cost Taxes and Charges', { frappe.ui.form.on('Landed Cost Taxes and Charges', {
amount: function(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); frm.events.calculate_amount(frm);
} }
},
expense_account: function(frm, cdt, cdn) {
frm.events.set_account_currency(frm, cdt, cdn);
}
}); });
erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({

View File

@ -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.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.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
from erpnext.accounts.general_ledger import process_gl_map from erpnext.accounts.general_ledger import process_gl_map
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
import json import json
from six import string_types, itervalues, iteritems from six import string_types, itervalues, iteritems
@ -195,7 +196,7 @@ class StockEntry(StockController):
and (sed.t_warehouse is null or sed.t_warehouse = '')""", self.project, as_list=1) and (sed.t_warehouse is null or sed.t_warehouse = '')""", self.project, as_list=1)
amount = amount[0][0] if amount else 0 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 from
`tabStock Entry` se, `tabLanded Cost Taxes and Charges` sed `tabStock Entry` se, `tabLanded Cost Taxes and Charges` sed
where where
@ -445,6 +446,7 @@ class StockEntry(StockController):
def calculate_rate_and_amount(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): 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) self.set_basic_rate(reset_outgoing_rate, raise_error_if_no_rate)
init_landed_taxes_and_totals(self)
self.distribute_additional_costs() self.distribute_additional_costs()
self.update_valuation_rate() self.update_valuation_rate()
self.set_total_incoming_outgoing_value() self.set_total_incoming_outgoing_value()
@ -533,7 +535,7 @@ class StockEntry(StockController):
if not any([d.item_code for d in self.items if d.t_warehouse]): if not any([d.item_code for d in self.items if d.t_warehouse]):
self.additional_costs = [] 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"): 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]) incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item])
@ -773,13 +775,19 @@ class StockEntry(StockController):
for d in self.get("items"): for d in self.get("items"):
if d.t_warehouse: if d.t_warehouse:
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) 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 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 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: if item_account_wise_additional_cost:
for d in self.get("items"): for d in self.get("items"):
for account, amount in iteritems(item_account_wise_additional_cost.get((d.item_code, d.name), {})): for account, amount in iteritems(item_account_wise_additional_cost.get((d.item_code, d.name), {})):
@ -790,7 +798,8 @@ class StockEntry(StockController):
"against": d.expense_account, "against": d.expense_account,
"cost_center": d.cost_center, "cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "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)) }, item=d))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
@ -798,7 +807,7 @@ class StockEntry(StockController):
"against": account, "against": account,
"cost_center": d.cost_center, "cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "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)) }, item=d))
return process_gl_map(gl_entries) return process_gl_map(gl_entries)

View File

@ -0,0 +1,62 @@
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;
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: {
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);
}
}
});
}
frm.refresh_field('taxes');
},
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)));
}
});
});