feat: Enhancement in landed cost voucher (#19252)

* feat: Enhancement in landed cost voucher

* fix: Make GL entries based on ledgers in Landed cost voucher

* fix: Patch to update expense account in Landed Cost Voucher and Stock Entry

* fix: Ability to select expense account in Stock Entry

* fix: Renaming and test case fixes

* fix: Test Cases

* fix: Additional cost in Stock Entry

* fix: Changed filters and test case fixes

* fix: Upadte filters in stokc entry expense account filter

* fix: company filter
This commit is contained in:
Deepesh Garg 2019-11-08 12:52:54 +05:30 committed by Nabin Hait
parent 7aef9f3b43
commit 7a23057eab
12 changed files with 202 additions and 169 deletions

View File

@ -25,6 +25,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_
unlink_inter_company_doc
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@ -436,6 +437,8 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
voucher_wise_stock_value = {}
if self.update_stock:
for d in frappe.get_all('Stock Ledger Entry',
@ -463,15 +466,16 @@ class PurchaseInvoice(BuyingController):
)
# Amount added through landed-cost-voucher
if flt(item.landed_cost_voucher_amount):
gl_entries.append(self.get_gl_dict({
"account": expenses_included_in_valuation,
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
"project": item.project
}, item=item))
if landed_cost_entries:
for account, amount in iteritems(landed_cost_entries[(item.item_code, item.name)]):
gl_entries.append(self.get_gl_dict({
"account": account,
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount),
"project": item.project
}, item=item))
# sub-contracting warehouse
if flt(item.rm_supp_cost):

View File

@ -776,6 +776,8 @@ def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
bom = frappe.get_doc('BOM', work_order.bom_no)
table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items'
expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company,
"expenses_included_in_valuation")
items = {}
for d in bom.get(table):
@ -786,6 +788,7 @@ def add_additional_cost(stock_entry, work_order):
for name in non_stock_items:
stock_entry.append('additional_costs', {
'expense_account': expenses_included_in_valuation,
'description': name[0],
'amount': items.get(name[0])
})

View File

@ -645,7 +645,8 @@ def make_stock_entry(work_order_id, purpose, qty=None):
stock_entry.to_warehouse = work_order.fg_warehouse
stock_entry.project = work_order.project
if purpose=="Manufacture":
additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty)
additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty,
company=work_order.company)
stock_entry.set("additional_costs", additional_costs)
stock_entry.set_stock_entry_type()

View File

@ -640,6 +640,7 @@ erpnext.patches.v12_0.create_default_energy_point_rules
erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order
erpnext.patches.v12_0.generate_leave_ledger_entries
erpnext.patches.v12_0.set_default_shopify_app_type
erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
erpnext.patches.v12_0.set_payment_entry_status
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields

View File

@ -0,0 +1,33 @@
from __future__ import unicode_literals
import frappe
from six import iteritems
def execute():
frappe.reload_doctype('Landed Cost Taxes and Charges')
company_account_map = frappe._dict(frappe.db.sql("""
SELECT name, expenses_included_in_valuation from `tabCompany`
"""))
for company, account in iteritems(company_account_map):
frappe.db.sql("""
UPDATE
`tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l
SET
t.expense_account = %s
WHERE
l.docstatus = 1
AND l.company = %s
AND t.parent = l.name
""", (account, company))
frappe.db.sql("""
UPDATE
`tabLanded Cost Taxes and Charges` t, `tabStock Entry` s
SET
t.expense_account = %s
WHERE
s.docstatus = 1
AND s.company = %s
AND t.parent = s.name
""", (account, company))

View File

@ -1,129 +1,51 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2014-07-11 11:51:00.453717",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2014-07-11 11:51:00.453717",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"expense_account",
"description",
"col_break3",
"amount"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"fieldname": "col_break3",
"fieldtype": "Column Break",
"width": "50%"
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "Company:company:default_currency",
"reqd": 1
},
{
"fieldname": "expense_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Expense Account",
"options": "Account",
"reqd": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-11-15 19:27:59.542487",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Taxes and Charges",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
],
"istable": 1,
"modified": "2019-09-30 18:28:32.070655",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Taxes and Charges",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -30,6 +30,16 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
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 {
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) {
@ -38,7 +48,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
<table class="table table-bordered" style="background-color: #f9f9f9;">
<tr><td>
<h4>
<i class="fa fa-hand-right"></i>
<i class="fa fa-hand-right"></i>
${__("Notes")}:
</h4>
<ul>
@ -96,7 +106,7 @@ 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) {
@ -105,7 +115,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
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[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
});

View File

@ -179,7 +179,7 @@ def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges=
lcv.set("taxes", [{
"description": "Insurance Charges",
"account": "_Test Account Insurance Charges - _TC",
"expense_account": "Expenses Included In Valuation - TCP1",
"amount": charges
}])

View File

@ -195,6 +195,7 @@ class PurchaseReceipt(BuyingController):
from erpnext.accounts.general_ledger import process_gl_map
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
landed_cost_entries = get_item_account_wise_additional_cost(self.name)
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
gl_entries = []
@ -233,15 +234,16 @@ class PurchaseReceipt(BuyingController):
negative_expense_to_be_booked += flt(d.item_tax_amount)
# Amount added through landed-cost-voucher
if flt(d.landed_cost_voucher_amount):
gl_entries.append(self.get_gl_dict({
"account": expenses_included_in_valuation,
"against": warehouse_account[d.warehouse]["account"],
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(d.landed_cost_voucher_amount),
"project": d.project
}, item=d))
if landed_cost_entries:
for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]):
gl_entries.append(self.get_gl_dict({
"account": account,
"against": warehouse_account[d.warehouse]["account"],
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount),
"project": d.project
}, item=d))
# sub-contracting warehouse
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
@ -584,3 +586,30 @@ def make_stock_entry(source_name,target_doc=None):
}, target_doc, set_missing_values)
return doclist
def get_item_account_wise_additional_cost(purchase_document):
landed_cost_voucher = frappe.get_value("Landed Cost Purchase Receipt",
{"receipt_document": purchase_document}, "parent")
if not landed_cost_voucher:
return
total_item_cost = 0
item_account_wise_cost = {}
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", landed_cost_voucher)
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
for item in landed_cost_voucher_doc.items:
if item.receipt_document == purchase_document:
total_item_cost += item.get(based_on_field)
for item in landed_cost_voucher_doc.items:
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] += \
account.amount * item.get(based_on_field) / total_item_cost
return item_account_wise_cost

View File

@ -68,6 +68,16 @@ 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");
},
@ -727,7 +737,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
return frappe.call({
method: "erpnext.stock.doctype.stock_entry.stock_entry.get_work_order_details",
args: {
work_order: me.frm.doc.work_order
work_order: me.frm.doc.work_order,
company: me.frm.doc.company
},
callback: function(r) {
if (!r.exc) {
@ -743,6 +754,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
if (me.frm.doc.purpose == "Manufacture") {
if (!me.frm.doc.to_warehouse) me.frm.set_value("to_warehouse", r.message["fg_warehouse"]);
if (r.message["additional_costs"].length) {
me.frm.clear_table("additional_costs");
$.each(r.message["additional_costs"], function(i, row) {
me.frm.add_child("additional_costs", row);
})

View File

@ -644,28 +644,37 @@ class StockEntry(StockController):
self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No')
def get_gl_entries(self, warehouse_account):
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
for d in self.get("items"):
additional_cost = flt(d.additional_cost, d.precision("additional_cost"))
if additional_cost:
gl_entries.append(self.get_gl_dict({
"account": expenses_included_in_valuation,
"against": d.expense_account,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": additional_cost
}, item=d))
total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
item_account_wise_additional_cost = {}
gl_entries.append(self.get_gl_dict({
"account": d.expense_account,
"against": expenses_included_in_valuation,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": -1 * additional_cost # put it as negative credit instead of debit purposefully
}, item=d))
for t in self.get("additional_costs"):
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)][t.expense_account] += \
(t.amount * d.basic_amount) / total_basic_amount
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), {})):
gl_entries.append(self.get_gl_dict({
"account": account,
"against": d.expense_account,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": amount
}, item=d))
gl_entries.append(self.get_gl_dict({
"account": d.expense_account,
"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
}, item=d))
return gl_entries
@ -1349,7 +1358,7 @@ def make_stock_in_entry(source_name, target_doc=None):
return doclist
@frappe.whitelist()
def get_work_order_details(work_order):
def get_work_order_details(work_order, company):
work_order = frappe.get_doc("Work Order", work_order)
pending_qty_to_produce = flt(work_order.qty) - flt(work_order.produced_qty)
@ -1360,14 +1369,17 @@ def get_work_order_details(work_order):
"wip_warehouse": work_order.wip_warehouse,
"fg_warehouse": work_order.fg_warehouse,
"fg_completed_qty": pending_qty_to_produce,
"additional_costs": get_additional_costs(work_order, fg_qty=pending_qty_to_produce)
"additional_costs": get_additional_costs(work_order, fg_qty=pending_qty_to_produce, company=company)
}
def get_additional_costs(work_order=None, bom_no=None, fg_qty=None):
def get_additional_costs(work_order=None, bom_no=None, fg_qty=None, company=None):
additional_costs = []
operating_cost_per_unit = get_operating_cost_per_unit(work_order, bom_no)
expenses_included_in_valuation = frappe.get_cached_value("Company", company, "expenses_included_in_valuation")
if operating_cost_per_unit:
additional_costs.append({
"expense_account": expenses_included_in_valuation,
"description": "Operating Cost as per Work Order / BOM",
"amount": operating_cost_per_unit * flt(fg_qty)
})
@ -1377,6 +1389,7 @@ def get_additional_costs(work_order=None, bom_no=None, fg_qty=None):
flt(work_order.additional_operating_cost) / flt(work_order.qty)
additional_costs.append({
"expense_account": expenses_included_in_valuation,
"description": "Additional Operating Cost",
"amount": additional_operating_cost_per_unit * flt(fg_qty)
})

View File

@ -259,6 +259,8 @@ class TestStockEntry(unittest.TestCase):
repack.posting_date = nowdate()
repack.posting_time = nowtime()
expenses_included_in_valuation = frappe.get_value("Company", company, "expenses_included_in_valuation")
items = get_multiple_items()
repack.items = []
for item in items:
@ -266,11 +268,13 @@ class TestStockEntry(unittest.TestCase):
repack.set("additional_costs", [
{
"description": "Actual Oerating Cost",
"expense_account": expenses_included_in_valuation,
"description": "Actual Operating Cost",
"amount": 1000
},
{
"description": "additional operating costs",
"expense_account": expenses_included_in_valuation,
"description": "Additional Operating Cost",
"amount": 200
},
])