diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 51c0007ec03..eae1818d20c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -40,7 +40,6 @@ from erpnext.assets.doctype.asset_category.asset_category import get_asset_categ from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.buying_controller import BuyingController -from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( update_billed_amount_based_on_po, ) @@ -460,11 +459,12 @@ class PurchaseInvoice(BuyingController): self.asset_received_but_not_billed = None + inventory_account_map = {} if self.update_stock: self.validate_item_code() self.validate_warehouse(for_validate) if auto_accounting_for_stock: - warehouse_account = get_warehouse_account_map(self.company) + inventory_account_map = self.get_inventory_account_map() for item in self.get("items"): # in case of auto inventory accounting, @@ -481,21 +481,19 @@ class PurchaseInvoice(BuyingController): ) ): if self.update_stock and item.warehouse and (not item.from_warehouse): - if ( - for_validate - and item.expense_account - and item.expense_account != warehouse_account[item.warehouse]["account"] - ): + _inv_dict = self.get_inventory_account_dict(item, inventory_account_map) + + if for_validate and item.expense_account and item.expense_account != _inv_dict["account"]: msg = _( "Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account" ).format( item.idx, - frappe.bold(warehouse_account[item.warehouse]["account"]), + frappe.bold(_inv_dict["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse), ) frappe.msgprint(msg, title=_("Expense Head Changed")) - item.expense_account = warehouse_account[item.warehouse]["account"] + item.expense_account = _inv_dict["account"] else: # check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not if item.purchase_receipt: @@ -857,7 +855,7 @@ class PurchaseInvoice(BuyingController): party=self.supplier, ) - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, inventory_account_map=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) if self.auto_accounting_for_stock: @@ -947,7 +945,7 @@ class PurchaseInvoice(BuyingController): # item gl entries stock_items = self.get_stock_items() if self.update_stock and self.auto_accounting_for_stock: - warehouse_account = get_warehouse_account_map(self.company) + inventory_account_map = self.get_inventory_account_map() landed_cost_entries = self.get_item_account_wise_lcv_entries() @@ -997,18 +995,24 @@ class PurchaseInvoice(BuyingController): ) if item.from_warehouse: + _inv_dict = self.get_inventory_account_dict(item, inventory_account_map) + + _inv_dict_from_warehouse = self.get_inventory_account_dict( + item, inventory_account_map, "from_warehouse" + ) + gl_entries.append( self.get_gl_dict( { - "account": warehouse_account[item.warehouse]["account"], - "against": warehouse_account[item.from_warehouse]["account"], + "account": _inv_dict["account"], + "against": _inv_dict_from_warehouse["account"], "cost_center": item.cost_center, "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": warehouse_debit_amount, "debit_in_transaction_currency": item.net_amount, }, - warehouse_account[item.warehouse]["account_currency"], + _inv_dict["account_currency"], item=item, ) ) @@ -1021,15 +1025,15 @@ class PurchaseInvoice(BuyingController): gl_entries.append( self.get_gl_dict( { - "account": warehouse_account[item.from_warehouse]["account"], - "against": warehouse_account[item.warehouse]["account"], + "account": _inv_dict_from_warehouse["account"], + "against": _inv_dict["account"], "cost_center": item.cost_center, "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": -1 * flt(credit_amount, item.precision("base_net_amount")), "debit_in_transaction_currency": item.net_amount, }, - warehouse_account[item.from_warehouse]["account_currency"], + _inv_dict_from_warehouse["account_currency"], item=item, ) ) @@ -1097,15 +1101,19 @@ class PurchaseInvoice(BuyingController): # sub-contracting warehouse if flt(item.rm_supp_cost): - supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"] - if not supplier_warehouse_account: + supplier_wh_dict = self.get_inventory_account_dict( + item, inventory_account_map, "supplier_warehouse" + ) + + supplier_inventory_account = supplier_wh_dict["account"] + if not supplier_inventory_account: frappe.throw( _("Please set account in Warehouse {0}").format(self.supplier_warehouse) ) gl_entries.append( self.get_gl_dict( { - "account": supplier_warehouse_account, + "account": supplier_inventory_account, "against": item.expense_account, "cost_center": item.cost_center, "project": item.project or self.project, @@ -1113,7 +1121,7 @@ class PurchaseInvoice(BuyingController): "credit": flt(item.rm_supp_cost), "credit_in_transaction_currency": item.net_amount, }, - warehouse_account[self.supplier_warehouse]["account_currency"], + supplier_wh_dict["account_currency"], item=item, ) ) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 11614467472..c656cd66bfd 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -101,8 +101,8 @@ class RepostAccountingLedger(Document): if doc.doctype in ["Payment Entry", "Journal Entry"]: gle_map = doc.build_gl_map() elif doc.doctype == "Purchase Receipt": - warehouse_account_map = get_warehouse_account_map(doc.company) - gle_map = doc.get_gl_entries(warehouse_account_map) + inventory_account_map = doc.get_inventory_account_map() + gle_map = doc.get_gl_entries(inventory_account_map) else: gle_map = doc.get_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fbf68ed0be2..5f6fe525fa8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1551,7 +1551,7 @@ class SalesInvoice(SellingController): elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock): make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, inventory_account_map=None): from erpnext.accounts.general_ledger import merge_similar_entries gl_entries = [] diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6e43de63ec6..03ac3ebab4f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1531,7 +1531,8 @@ def repost_gle_for_stock_vouchers( voucher_obj = frappe.get_lazy_doc(voucher_type, voucher_no) # Some transactions post credit as negative debit, this is handled while posting GLE # but while comparing we need to make sure it's flipped so comparisons are accurate - expected_gle = toggle_debit_credit_if_negative(voucher_obj.get_gl_entries(warehouse_account)) + inventory_account_map = voucher_obj.get_inventory_account_map() + expected_gle = toggle_debit_credit_if_negative(voucher_obj.get_gl_entries(inventory_account_map)) if expected_gle: if not existing_gle or not compare_existing_and_expected_gle( existing_gle, expected_gle, precision diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index eeeb3ab849f..e1c8b16f735 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -24,7 +24,6 @@ from erpnext.assets.doctype.asset_category.asset_category import get_asset_categ from erpnext.controllers.stock_controller import StockController from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults -from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.get_item_details import ( ItemDetailsCtx, @@ -412,13 +411,15 @@ class AssetCapitalization(StockController): elif self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): + def get_gl_entries( + self, inventory_account_map=None, default_expense_account=None, default_cost_center=None + ): # Stock GL Entries gl_entries = [] - self.warehouse_account = warehouse_account - if not self.warehouse_account: - self.warehouse_account = get_warehouse_account_map(self.company) + self.inventory_account_map = inventory_account_map + if not self.inventory_account_map: + self.inventory_account_map = self.get_inventory_account_map() precision = self.get_debit_field_precision() self.sle_map = self.get_stock_ledger_details() @@ -457,11 +458,12 @@ class AssetCapitalization(StockController): for item_row in self.stock_items: sle_list = self.sle_map.get(item_row.name) if sle_list: + _inv_dict = self.get_inventory_account_dict(item_row, self.inventory_account_map) for sle in sle_list: stock_value_difference = flt(sle.stock_value_difference, precision) if erpnext.is_perpetual_inventory_enabled(self.company): - account = self.warehouse_account[sle.warehouse]["account"] + account = _inv_dict["account"] else: account = self.get_company_default("default_expense_account") @@ -476,7 +478,7 @@ class AssetCapitalization(StockController): "remarks": self.get("remarks") or "Accounting Entry for Stock", "credit": -1 * stock_value_difference, }, - self.warehouse_account[sle.warehouse]["account_currency"], + _inv_dict["account_currency"], item=item_row, ) ) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b0a417a8377..8bc3b50d198 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -22,10 +22,13 @@ from erpnext.controllers.sales_and_purchase_return import ( filter_serial_batches, make_serial_batch_bundle_for_return, ) +from erpnext.setup.doctype.brand.brand import get_brand_defaults +from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.inventory_dimension.inventory_dimension import ( get_evaluated_inventory_dimension, ) +from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( combine_datetime, get_type_of_transaction, @@ -152,6 +155,62 @@ class StockController(AccountsController): ) ) + def get_item_wise_inventory_account_map(self, company): + inventory_account_map = frappe._dict() + for table in ["items", "packed_items", "supplied_items"]: + if not self.get(table): + continue + + _map = get_item_wise_inventory_account_map(self.get(table), self.company) + inventory_account_map.update(_map) + + return inventory_account_map + + @property + def use_item_inventory_account(self): + return frappe.get_cached_value("Company", self.company, "enable_item_wise_inventory_account") + + def get_inventory_account_dict(self, row, inventory_account_map, warehouse_field=None): + account_dict = frappe._dict() + + if isinstance(row, dict): + row = frappe._dict(row) + + if self.use_item_inventory_account: + item_code = ( + row.rm_item_code if hasattr(row, "rm_item_code") and row.rm_item_code else row.item_code + ) + + account_dict = inventory_account_map.get(item_code) + + if not account_dict: + frappe.throw( + _( + "Please set default inventory account for item {0}, or their item group or brand." + ).format(bold(item_code)) + ) + + if account_dict: + return account_dict + + if not warehouse_field: + warehouse_field = "warehouse" + + warehouse = row.get(warehouse_field) + if not warehouse: + warehouse = self.get(warehouse_field) + + if warehouse and warehouse in inventory_account_map: + account_dict = inventory_account_map[warehouse] + + return account_dict + + def get_inventory_account_map(self): + if self.use_item_inventory_account: + return self.get_item_wise_inventory_account_map(self.company) + + return get_warehouse_account_map(self.company) + def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False): if self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) @@ -169,14 +228,14 @@ class StockController(AccountsController): or provisional_accounting_for_non_stock_items or is_asset_pr ): - warehouse_account = get_warehouse_account_map(self.company) + inventory_account_map = self.get_inventory_account_map() if self.docstatus == 1: if not gl_entries: gl_entries = ( - self.get_gl_entries(warehouse_account, via_landed_cost_voucher) + self.get_gl_entries(inventory_account_map, via_landed_cost_voucher) if self.doctype == "Purchase Receipt" - else self.get_gl_entries(warehouse_account) + else self.get_gl_entries(inventory_account_map) ) make_gl_entries(gl_entries, from_repost=from_repost) @@ -578,9 +637,11 @@ class StockController(AccountsController): for row in self.items: row.use_serial_batch_fields = 1 - def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): - if not warehouse_account: - warehouse_account = get_warehouse_account_map(self.company) + def get_gl_entries( + self, inventory_account_map=None, default_expense_account=None, default_cost_center=None + ): + if not inventory_account_map: + inventory_account_map = self.get_inventory_account_map() sle_map = self.get_stock_ledger_details() voucher_details = self.get_voucher_details(default_expense_account, default_cost_center, sle_map) @@ -593,7 +654,9 @@ class StockController(AccountsController): sle_rounding_diff = 0.0 if sle_list: for sle in sle_list: - if warehouse_account.get(sle.warehouse): + _inv_dict = self.get_inventory_account_dict(sle, inventory_account_map) + + if _inv_dict.get("account"): # from warehouse account sle_rounding_diff += flt(sle.stock_value_difference) @@ -602,15 +665,17 @@ class StockController(AccountsController): # expense account/ target_warehouse / source_warehouse if item_row.get("target_warehouse"): - warehouse = item_row.get("target_warehouse") - expense_account = warehouse_account[warehouse]["account"] + _target_wh_inv_dict = self.get_inventory_account_dict( + item_row, inventory_account_map, warehouse_field="target_warehouse" + ) + expense_account = _target_wh_inv_dict["account"] else: expense_account = item_row.expense_account gl_list.append( self.get_gl_dict( { - "account": warehouse_account[sle.warehouse]["account"], + "account": _inv_dict["account"], "against": expense_account, "cost_center": item_row.cost_center, "project": sle.get("project") or item_row.project or self.get("project"), @@ -620,7 +685,7 @@ class StockController(AccountsController): or self.get("is_opening") or "No", }, - warehouse_account[sle.warehouse]["account_currency"], + _inv_dict["account_currency"], item=item_row, ) ) @@ -629,7 +694,7 @@ class StockController(AccountsController): self.get_gl_dict( { "account": expense_account, - "against": warehouse_account[sle.warehouse]["account"], + "against": _inv_dict["account"], "cost_center": item_row.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": -1 * flt(sle.stock_value_difference, precision), @@ -649,9 +714,15 @@ class StockController(AccountsController): if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer(): warehouse_asset_account = "" if self.get("is_internal_customer"): - warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] + _inv_dict = self.get_inventory_account_dict( + item_row, inventory_account_map, warehouse_field="target_warehouse" + ) + + warehouse_asset_account = _inv_dict["account"] elif self.get("is_internal_supplier"): - warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] + _inv_dict = self.get_inventory_account_dict(item_row, inventory_account_map) + + warehouse_asset_account = _inv_dict["account"] expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account") if not expense_account: @@ -672,7 +743,7 @@ class StockController(AccountsController): "debit": sle_rounding_diff, "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", }, - warehouse_account[sle.warehouse]["account_currency"], + _inv_dict["account_currency"], item=item_row, ) ) @@ -2015,3 +2086,49 @@ def make_bundle_for_material_transfer(**kwargs): bundle_doc.submit() return bundle_doc.name + + +def get_item_wise_inventory_account_map(rows, company): + # returns dict of item_code and its inventory account details + # Example: {"ITEM-001": {"account": "Stock - ABC", "account_currency": "INR"}, ...} + + inventory_map = frappe._dict() + + for row in rows: + item_code = row.rm_item_code if hasattr(row, "rm_item_code") else row.item_code + if not item_code: + continue + + if inventory_map.get(item_code): + continue + + item_defaults = get_item_defaults(item_code, company) + if item_defaults.default_inventory_account: + inventory_map[item_code] = frappe._dict( + { + "account": item_defaults.default_inventory_account, + "account_currency": item_defaults.inventory_account_currency, + } + ) + + if not inventory_map.get(item_code): + item_group_defaults = get_item_group_defaults(item_code, company) + if item_group_defaults.default_inventory_account: + inventory_map[item_code] = frappe._dict( + { + "account": item_group_defaults.default_inventory_account, + "account_currency": item_group_defaults.inventory_account_currency, + } + ) + + if not inventory_map.get(item_code): + brand_defaults = get_brand_defaults(item_code, company) + if brand_defaults.default_inventory_account: + inventory_map[item_code] = frappe._dict( + { + "account": brand_defaults.default_inventory_account, + "account_currency": brand_defaults.inventory_account_currency, + } + ) + + return inventory_map diff --git a/erpnext/controllers/tests/test_item_wise_inventory_account.py b/erpnext/controllers/tests/test_item_wise_inventory_account.py new file mode 100644 index 00000000000..01ae69265ac --- /dev/null +++ b/erpnext/controllers/tests/test_item_wise_inventory_account.py @@ -0,0 +1,509 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import copy +from collections import defaultdict + +import frappe +from frappe.tests import IntegrationTestCase +from frappe.utils import cint + +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record +from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry +from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + + +class TestItemWiseInventoryAccount(IntegrationTestCase): + def setUp(self): + self.company = make_company() + self.company_abbr = frappe.db.get_value("Company", self.company, "abbr") + self.default_warehouse = frappe.db.get_value( + "Warehouse", + {"company": self.company, "is_group": 0, "warehouse_name": ("like", "%Stores%")}, + ) + + def test_item_account_for_purchase_receipt_entry(self): + items = { + "Stock Item A": {"is_stock_item": 1}, + "Stock Item B": {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SER-TT-.####"}, + } + + for item_name, item_data in items.items(): + item = make_item( + item_name, + properties=item_data, + ) + + account = self.add_inventory_account(item) + items[item_name]["account"] = account + + pr = make_purchase_receipt( + item_code="Stock Item A", + qty=5, + rate=100, + warehouse=self.default_warehouse, + company=self.company, + do_not_submit=True, + ) + + pr.append( + "items", + { + "item_code": "Stock Item B", + "qty": 2, + "rate": 200, + "warehouse": self.default_warehouse, + }, + ) + + pr.submit() + + for row in items: + item_code = row + account = items[item_code]["account"] + + sle_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "item_code": item_code}, + "stock_value_difference", + ) + + gl_value = frappe.db.get_value( + "GL Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "account": account, + }, + "debit", + ) + + self.assertEqual(sle_value, gl_value, f"GL Entry not created for {item_code} correctly") + + def test_item_account_for_delivery_note_entry(self): + items = { + "Stock Item A": {"is_stock_item": 1}, + "Stock Item B": {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SER-TT-.####"}, + } + + for item_name, item_data in items.items(): + item = make_item( + item_name, + properties=item_data, + ) + + account = self.add_inventory_account(item) + items[item_name]["account"] = account + + pr = make_purchase_receipt( + item_code="Stock Item A", + qty=5, + rate=100, + warehouse=self.default_warehouse, + company=self.company, + do_not_submit=True, + ) + + pr.append( + "items", + { + "item_code": "Stock Item B", + "qty": 2, + "rate": 200, + "warehouse": self.default_warehouse, + }, + ) + + pr.submit() + + dn = create_delivery_note( + item_code="Stock Item A", + qty=5, + rate=200, + warehouse=self.default_warehouse, + company=self.company, + cost_center=frappe.db.get_value("Company", self.company, "cost_center"), + expense_account=frappe.db.get_value("Company", self.company, "default_expense_account"), + do_not_submit=True, + ) + + dn.append( + "items", + { + "item_code": "Stock Item B", + "qty": 2, + "rate": 300, + "warehouse": self.default_warehouse, + }, + ) + + dn.submit() + + for row in items: + item_code = row + account = items[item_code]["account"] + + sle_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": dn.name, "item_code": item_code}, + "stock_value_difference", + ) + + gl_value = ( + frappe.db.get_value( + "GL Entry", + { + "voucher_type": "Delivery Note", + "voucher_no": dn.name, + "account": account, + }, + "credit", + ) + * -1 + ) + + self.assertEqual(sle_value, gl_value, f"GL Entry not created for {item_code} correctly") + + def test_item_group_account_for_purchase_receipt_entry(self): + items = { + "Stock Item C": {"is_stock_item": 1, "item_group": "Test Item Group C"}, + "Stock Item C1": {"is_stock_item": 1, "item_group": "Test Item Group C", "qty": 3, "rate": 150}, + "Stock Item D": { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SER-TT-.####", + "item_group": "Test Item Group D", + "qty": 2, + "rate": 250, + }, + "Stock Item D1": {"is_stock_item": 1, "item_group": "Test Item Group D", "qty": 4, "rate": 300}, + } + + for row in items: + self.make_item_group(items[row]["item_group"]) + + inventory_account_dict = frappe._dict() + for item_name, item_data in items.items(): + item_data = frappe._dict(item_data) + make_item( + item_name, + properties=item_data, + ) + + item_group = frappe.get_doc("Item Group", item_data.item_group) + account = self.add_inventory_account(item_group, "item_group_defaults") + inventory_account_dict[item_data.item_group] = account + + pr = make_purchase_receipt( + item_code="Stock Item C", + qty=5, + rate=100, + warehouse=self.default_warehouse, + company=self.company, + do_not_submit=True, + ) + + for item_code, values in items.items(): + if item_code == "Stock Item C": + continue + + pr.append( + "items", + { + "item_code": item_code, + "qty": values.get("qty", 1), + "rate": values.get("rate", 200), + "warehouse": self.default_warehouse, + }, + ) + + pr.submit() + + for item_group, account in inventory_account_dict.items(): + items = frappe.get_all( + "Item", + filters={"item_group": item_group}, + pluck="name", + ) + + sle_value = frappe.get_all( + "Stock Ledger Entry", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "item_code": ("in", items), + }, + fields=["sum(stock_value_difference) as value"], + ) + + gl_value = frappe.db.get_value( + "GL Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "account": account, + }, + "debit", + ) + + self.assertEqual(sle_value[0].value, gl_value, f"GL Entry not created for {item_code} correctly") + + def test_item_group_account_for_delivery_note_entry(self): + items = { + "Stock Item E": {"is_stock_item": 1, "item_group": "Test Item Group E"}, + "Stock Item E1": {"is_stock_item": 1, "item_group": "Test Item Group E", "qty": 3, "rate": 150}, + "Stock Item F": { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SER-TT-.####", + "item_group": "Test Item Group F", + "qty": 2, + "rate": 250, + }, + "Stock Item F1": {"is_stock_item": 1, "item_group": "Test Item Group F", "qty": 4, "rate": 300}, + } + + for row in items: + self.make_item_group(items[row]["item_group"]) + + inventory_account_dict = frappe._dict() + for item_name, item_data in items.items(): + item_data = frappe._dict(item_data) + make_item( + item_name, + properties=item_data, + ) + + item_group = frappe.get_doc("Item Group", item_data.item_group) + account = self.add_inventory_account(item_group, "item_group_defaults") + inventory_account_dict[item_data.item_group] = account + + pr = make_purchase_receipt( + item_code="Stock Item E", + qty=5, + rate=100, + warehouse=self.default_warehouse, + company=self.company, + do_not_submit=True, + ) + + for item_code, values in items.items(): + if item_code == "Stock Item E": + continue + + pr.append( + "items", + { + "item_code": item_code, + "qty": values.get("qty", 1), + "rate": values.get("rate", 200), + "warehouse": self.default_warehouse, + }, + ) + + pr.submit() + + dn = create_delivery_note( + item_code="Stock Item E", + qty=5, + rate=200, + warehouse=self.default_warehouse, + company=self.company, + cost_center=frappe.db.get_value("Company", self.company, "cost_center"), + expense_account=frappe.db.get_value("Company", self.company, "default_expense_account"), + do_not_submit=True, + ) + + for item_code, values in items.items(): + if item_code == "Stock Item E": + continue + + dn.append( + "items", + { + "item_code": item_code, + "qty": values.get("qty", 1), + "rate": values.get("rate", 200), + "warehouse": self.default_warehouse, + }, + ) + + dn.submit() + + for item_group, account in inventory_account_dict.items(): + items = frappe.get_all( + "Item", + filters={"item_group": item_group}, + pluck="name", + ) + + sle_value = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": "Delivery Note", "voucher_no": dn.name, "item_code": ("in", items)}, + fields=["sum(stock_value_difference) as value"], + ) + + gl_value = ( + frappe.db.get_value( + "GL Entry", + { + "voucher_type": "Delivery Note", + "voucher_no": dn.name, + "account": account, + }, + "credit", + ) + * -1 + ) + + self.assertEqual(sle_value[0].value, gl_value, f"GL Entry not created for {item_code} correctly") + + def make_item_group(self, item_name): + if not frappe.db.exists("Item Group", item_name): + item_group = frappe.get_doc( + { + "doctype": "Item Group", + "item_group_name": item_name, + "is_group": 0, + } + ) + item_group.insert() + return item_group + + return frappe.get_doc("Item Group", item_name) + + def add_inventory_account(self, item, table_name=None): + if not table_name: + table_name = "item_defaults" + + account = item.name + " - " + self.company_abbr + if not frappe.db.exists("Account", account): + account_doc = frappe.get_doc( + { + "doctype": "Account", + "account_name": item.name, + "account_type": "Stock", + "company": self.company, + "is_group": 0, + "parent_account": "Stock Assets - " + self.company_abbr, + } + ) + account_doc.insert() + + if not frappe.db.get_value("Item Default", {"parent": item.name, "company": self.company}): + item.append( + table_name, + { + "company": self.company, + "default_inventory_account": account, + "default_warehouse": self.default_warehouse, + }, + ) + item.save() + + return account + + def test_item_account_for_manufacture_entry(self): + items = { + "Stock Item A1": {"is_stock_item": 1}, + "Stock Item B1": {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SER-TT-.####"}, + } + + for item_name, item_data in items.items(): + item = make_item( + item_name, + properties=item_data, + ) + + account = self.add_inventory_account(item) + items[item_name]["account"] = account + + make_purchase_receipt( + item_code="Stock Item B1", + qty=5, + rate=100, + warehouse=self.default_warehouse, + company=self.company, + ) + + bom = make_bom( + item="Stock Item A1", + company=self.company, + source_warehouse=self.default_warehouse, + raw_materials=["Stock Item B1"], + ) + + wip_warehouse = frappe.db.get_value( + "Warehouse", + {"company": self.company, "is_group": 0, "warehouse_name": ("like", "%Work In Progress%")}, + ) + + fg_warehouse = frappe.db.get_value( + "Warehouse", + {"company": self.company, "is_group": 0, "warehouse_name": ("like", "%Finished Goods%")}, + ) + + wo_order = make_wo_order_test_record( + item="Stock Item A1", + qty=5, + company=self.company, + source_warehouse=self.default_warehouse, + bom=bom.name, + wip_warehouse=wip_warehouse, + fg_warehouse=fg_warehouse, + ) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 5)) + stock_entry.submit() + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) + stock_entry.submit() + + for row in stock_entry.items: + item_code = row.item_code + account = items[item_code]["account"] + + sle_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Stock Entry", "voucher_no": stock_entry.name, "item_code": item_code}, + "stock_value_difference", + ) + + field = "debit" if row.t_warehouse == fg_warehouse else "credit" + gl_value = frappe.db.get_value( + "GL Entry", + { + "voucher_type": "Stock Entry", + "voucher_no": stock_entry.name, + "account": account, + }, + field, + ) + + if row.s_warehouse: + gl_value = gl_value * -1 + + self.assertEqual(sle_value, gl_value, f"GL Entry not created for {item_code} correctly") + + +def make_company(): + company = "_Test Company for Item Wise Inventory Account" + if frappe.db.exists("Company", company): + return company + + company = frappe.get_doc( + { + "doctype": "Company", + "company_name": "_Test Company for Item Wise Inventory Account", + "abbr": "_TCIWIA", + "default_currency": "INR", + "country": "India", + "enable_perpetual_inventory": 1, + "enable_item_wise_inventory_account": 1, + } + ).insert() + + return company.name diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 349fe5c0771..b5cbc3907d7 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -114,10 +114,11 @@ "stock_tab", "auto_accounting_for_stock_settings", "enable_perpetual_inventory", + "enable_item_wise_inventory_account", "enable_provisional_accounting_for_non_stock_items", "default_inventory_account", - "stock_adjustment_account", "column_break_32", + "stock_adjustment_account", "stock_received_but_not_billed", "default_provisional_account", "default_in_transit_warehouse", @@ -877,6 +878,13 @@ "fieldtype": "Link", "label": "Service Expense Account", "options": "Account" + }, + { + "default": "0", + "description": "If enabled, the system will use the inventory account set in the Item Master or Item Group or Brand. Otherwise, it will use the inventory account set in the Warehouse.", + "fieldname": "enable_item_wise_inventory_account", + "fieldtype": "Check", + "label": "Enable Item-wise Inventory Account" } ], "icon": "fa fa-building", @@ -884,7 +892,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2025-10-10 15:12:37.941251", + "modified": "2025-10-23 13:15:52.411984", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index a56061f43f6..b185bc4c871 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -6,7 +6,7 @@ import json import frappe import frappe.defaults -from frappe import _ +from frappe import _, bold from frappe.cache_manager import clear_defaults_cache from frappe.contacts.address_and_contact import load_address_and_contact from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -73,6 +73,7 @@ class Company(NestedSet): disposal_account: DF.Link | None domain: DF.Data | None email: DF.Data | None + enable_item_wise_inventory_account: DF.Check enable_perpetual_inventory: DF.Check enable_provisional_accounting_for_non_stock_items: DF.Check exception_budget_approver_role: DF.Link | None @@ -158,6 +159,24 @@ class Company(NestedSet): self.set_chart_of_accounts() self.validate_parent_company() self.set_reporting_currency() + self.validate_inventory_account_settings() + + def validate_inventory_account_settings(self): + doc_before_save = self.get_doc_before_save() + if not doc_before_save: + return + + if ( + doc_before_save.enable_item_wise_inventory_account != self.enable_item_wise_inventory_account + and frappe.db.get_value("Stock Ledger Entry", {"is_cancelled": 0, "company": self.name}, "name") + and doc_before_save.enable_perpetual_inventory + ): + frappe.throw( + _( + "Cannot enable Item-wise Inventory Account, as there are existing Stock Ledger Entries for the company {0} with Warehouse-wise Inventory Account. Please cancel the stock transactions first and try again." + ).format(bold(self.name)), + title=_("Cannot Change Inventory Account Setting"), + ) def validate_abbr(self): if not self.abbr: @@ -455,6 +474,22 @@ class Company(NestedSet): _("Set default inventory account for perpetual inventory"), alert=True, indicator="orange" ) + doc_before_save = self.get_doc_before_save() + if not doc_before_save: + return + + if ( + doc_before_save.enable_perpetual_inventory + and not self.enable_perpetual_inventory + and doc_before_save.enable_item_wise_inventory_account != self.enable_item_wise_inventory_account + ): + if frappe.db.get_value("Stock Ledger Entry", {"is_cancelled": 0, "company": self.name}, "name"): + frappe.throw( + _( + "Cannot disable perpetual inventory, as there are existing Stock Ledger Entries for the company {0}. Please cancel the stock transactions first and try again." + ).format(bold(self.name)) + ) + def validate_provisional_account_for_non_stock_items(self): if not self.get("__islocal"): if ( diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 525b083f5f0..c48e62159c1 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -509,6 +509,17 @@ $.extend(erpnext.item, { }; }); }); + + frm.set_query("default_inventory_account", "item_defaults", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + is_group: 0, + company: row.company, + account_type: "Stock", + }, + }; + }); }, make_dashboard: function (frm) { diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 67e511225f7..1926d334f98 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -10,6 +10,8 @@ "column_break_3", "default_price_list", "default_discount_account", + "default_inventory_account", + "inventory_account_currency", "purchase_defaults", "buying_cost_center", "default_supplier", @@ -168,11 +170,25 @@ "fieldtype": "Link", "label": "Purchase Expense Contra Account", "options": "Account" + }, + { + "fieldname": "default_inventory_account", + "fieldtype": "Link", + "label": "Default Inventory Account", + "options": "Account" + }, + { + "fetch_from": "default_inventory_account.account_currency", + "fieldname": "inventory_account_currency", + "fieldtype": "Link", + "label": "Inventory Account Currency", + "options": "Currency", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2025-10-01 19:17:33.687836", + "modified": "2025-10-21 10:50:46.144721", "modified_by": "Administrator", "module": "Stock", "name": "Item Default", diff --git a/erpnext/stock/doctype/item_default/item_default.py b/erpnext/stock/doctype/item_default/item_default.py index b24036da420..10651d260c6 100644 --- a/erpnext/stock/doctype/item_default/item_default.py +++ b/erpnext/stock/doctype/item_default/item_default.py @@ -18,6 +18,7 @@ class ItemDefault(Document): company: DF.Link default_cogs_account: DF.Link | None default_discount_account: DF.Link | None + default_inventory_account: DF.Link | None default_price_list: DF.Link | None default_provisional_account: DF.Link | None default_supplier: DF.Link | None @@ -26,6 +27,7 @@ class ItemDefault(Document): deferred_revenue_account: DF.Link | None expense_account: DF.Link | None income_account: DF.Link | None + inventory_account_currency: DF.Link | None parent: DF.Data parentfield: DF.Data parenttype: DF.Data diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c512ca97eac..36b759fedca 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -472,19 +472,19 @@ class PurchaseReceipt(BuyingController): for item in self.items: item.amount_difference_with_purchase_invoice = 0 - def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False): + def get_gl_entries(self, inventory_account_map=None, via_landed_cost_voucher=False): from erpnext.accounts.general_ledger import process_gl_map gl_entries = [] - self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) + self.make_item_gl_entries(gl_entries, inventory_account_map=inventory_account_map) self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher) self.set_gl_entry_for_purchase_expense(gl_entries) update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) - def make_item_gl_entries(self, gl_entries, warehouse_account=None): + def make_item_gl_entries(self, gl_entries, inventory_account_map=None): from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( get_purchase_document_details, ) @@ -526,9 +526,11 @@ class PurchaseReceipt(BuyingController): ): return 0.0 - account = ( - warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb - ) + account = stock_asset_rbnb + if item.from_warehouse: + _inv_dict = self.get_inventory_account_dict(item, inventory_account_map, "from_warehouse") + account = _inv_dict["account"] + account_currency = get_account_currency(account) # GL Entry for from warehouse or Stock Received but not billed @@ -653,7 +655,7 @@ class PurchaseReceipt(BuyingController): def make_sub_contracting_gl_entries(item): # sub-contracting warehouse - if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): + if flt(item.rm_supp_cost) and supplier_warehouse_account: self.add_gl_entry( gl_entries=gl_entries, account=supplier_warehouse_account, @@ -748,22 +750,25 @@ class PurchaseReceipt(BuyingController): stock_value_diff = ( flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount) ) - elif warehouse_account.get(d.warehouse): + elif inventory_account := self.get_inventory_account_dict(d, inventory_account_map): stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) - stock_asset_account_name = warehouse_account[d.warehouse]["account"] - supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get( - "account" - ) - supplier_warehouse_account_currency = warehouse_account.get( - self.supplier_warehouse, {} - ).get("account_currency") + stock_asset_account_name = inventory_account["account"] + + supplier_warehouse_account = None + supplier_warehouse_account_currency = None + if self.supplier_warehouse: + if _inv_dict := self.get_inventory_account_dict( + d, inventory_account_map, "supplier_warehouse" + ): + supplier_warehouse_account = _inv_dict["account"] + supplier_warehouse_account_currency = _inv_dict["account_currency"] # If PR is sub-contracted and fg item rate is zero # in that case if account for source and target warehouse are same, # then GL entries should not be posted if ( flt(stock_value_diff) == flt(d.rm_supp_cost) - and warehouse_account.get(self.supplier_warehouse) + and supplier_warehouse_account and stock_asset_account_name == supplier_warehouse_account ): continue @@ -795,7 +800,9 @@ class PurchaseReceipt(BuyingController): ) stock_value_diff = get_stock_value_difference(self.name, d.name, d.rejected_warehouse) - stock_asset_account_name = warehouse_account[d.rejected_warehouse]["account"] + _inv_dict = self.get_inventory_account_dict(d, inventory_account_map, "rejected_warehouse") + + stock_asset_account_name = _inv_dict["account"] make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) if not d.qty: diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index f24c826d508..b7605cc9276 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -1150,7 +1150,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2025-10-14 12:58:20.384056", + "modified": "2025-10-21 10:39:32.659933", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 9e850fda7a7..bd003212745 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1644,8 +1644,8 @@ class StockEntry(StockController, SubcontractingInwardController): sl_entries.append(sle) - def get_gl_entries(self, warehouse_account): - gl_entries = super().get_gl_entries(warehouse_account) + def get_gl_entries(self, inventory_account_map): + gl_entries = super().get_gl_entries(inventory_account_map) if self.purpose in ("Repack", "Manufacture"): total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) @@ -1720,11 +1720,11 @@ class StockEntry(StockController, SubcontractingInwardController): ) ) - self.set_gl_entries_for_landed_cost_voucher(gl_entries, warehouse_account) + self.set_gl_entries_for_landed_cost_voucher(gl_entries, inventory_account_map) return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) - def set_gl_entries_for_landed_cost_voucher(self, gl_entries, warehouse_account): + def set_gl_entries_for_landed_cost_voucher(self, gl_entries, inventory_account_map): landed_cost_entries = self.get_item_account_wise_lcv_entries() if not landed_cost_entries: return @@ -1742,11 +1742,12 @@ class StockEntry(StockController, SubcontractingInwardController): else flt(amount["amount"]) ) + _inv_dict = self.get_inventory_account_dict(item, inventory_account_map, "t_warehouse") gl_entries.append( self.get_gl_dict( { "account": account, - "against": warehouse_account.get(item.t_warehouse)["account"], + "against": _inv_dict["account"], "cost_center": item.cost_center, "debit": 0.0, "credit": credit_amount, @@ -1766,7 +1767,7 @@ class StockEntry(StockController, SubcontractingInwardController): self.get_gl_dict( { "account": item.expense_account, - "against": warehouse_account.get(item.t_warehouse)["account"], + "against": _inv_dict["account"], "cost_center": item.cost_center, "debit": 0.0, "credit": credit_amount * -1, diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 2969c502200..f5f7cdd41cb 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -944,11 +944,11 @@ class StockReconciliation(StockController): new_sl_entries.extend(merge_similar_entries.values()) return new_sl_entries - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, inventory_account_map=None): if not self.cost_center: msgprint(_("Please enter Cost Center"), raise_exception=1) - return super().get_gl_entries(warehouse_account, self.expense_account, self.cost_center) + return super().get_gl_entries(inventory_account_map, self.expense_account, self.cost_center) def validate_expense_account(self): if not cint(erpnext.is_perpetual_inventory_enabled(self.company)): diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index e6e52c395ea..78210c87447 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -545,7 +545,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-05-06 02:39:24.284587", + "modified": "2025-10-23 13:16:10.527190", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 7f6915ccd6d..e88f621f090 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -872,6 +872,18 @@ def get_default_income_account(ctx: ItemDetailsCtx, item, item_group, brand): ) +def get_default_inventory_account(ctx: ItemDetailsCtx, item, item_group, brand): + if not frappe.get_cached_value("Company", ctx.company, "enable_item_wise_inventory_account"): + return None + + return ( + ctx.inventory_account + or item.get("default_inventory_account") + or item_group.get("default_inventory_account") + or brand.get("default_inventory_account") + ) + + def get_default_expense_account(ctx: ItemDetailsCtx, item, item_group, brand): if ctx.get("doctype") in ["Sales Invoice", "Delivery Note"]: expense_account = ( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 72d434eea55..9a6fe76ecfe 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -596,19 +596,19 @@ class SubcontractingReceipt(SubcontractingController): "Subcontracting Receipt", self.name, "status", status, update_modified=update_modified ) - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, inventory_account_map=None): from erpnext.accounts.general_ledger import process_gl_map if not erpnext.is_perpetual_inventory_enabled(self.company): return [] gl_entries = [] - self.make_item_gl_entries(gl_entries, warehouse_account) - self.make_item_gl_entries_for_lcv(gl_entries, warehouse_account) + self.make_item_gl_entries(gl_entries, inventory_account_map) + self.make_item_gl_entries_for_lcv(gl_entries, inventory_account_map) return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) - def make_item_gl_entries(self, gl_entries, warehouse_account=None): + def make_item_gl_entries(self, gl_entries, inventory_account_map=None): warehouse_with_no_account = [] supplied_items_details = frappe._dict() @@ -625,7 +625,9 @@ class SubcontractingReceipt(SubcontractingController): for item in self.items: if flt(item.rate) and flt(item.qty): - if warehouse_account.get(item.warehouse): + _inv_dict = self.get_inventory_account_dict(item, inventory_account_map) + + if _inv_dict.get("account"): stock_value_diff = frappe.db.get_value( "Stock Ledger Entry", { @@ -638,22 +640,18 @@ class SubcontractingReceipt(SubcontractingController): "stock_value_difference", ) - accepted_warehouse_account = warehouse_account[item.warehouse]["account"] - supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get( - "account" - ) remarks = self.get("remarks") or _("Accounting Entry for Stock") # Accepted Warehouse Account (Debit) self.add_gl_entry( gl_entries=gl_entries, - account=accepted_warehouse_account, + account=_inv_dict["account"], cost_center=item.cost_center, debit=stock_value_diff, credit=0.0, remarks=remarks, against_account=item.expense_account, - account_currency=get_account_currency(accepted_warehouse_account), + account_currency=_inv_dict["account_currency"], project=item.project, item=item, ) @@ -669,7 +667,7 @@ class SubcontractingReceipt(SubcontractingController): debit=0.0, credit=flt(stock_value_diff) - service_cost, remarks=remarks, - against_account=accepted_warehouse_account, + against_account=_inv_dict["account"], account_currency=get_account_currency(item.expense_account), project=item.project, item=item, @@ -684,24 +682,28 @@ class SubcontractingReceipt(SubcontractingController): debit=0.0, credit=service_cost, remarks=remarks, - against_account=accepted_warehouse_account, + against_account=_inv_dict["account"], account_currency=get_account_currency(service_account), project=item.project, item=item, ) - if flt(item.rm_supp_cost) and supplier_warehouse_account: + if flt(item.rm_supp_cost): for rm_item in supplied_items_details.get(item.name): + _inv_dict = self.get_inventory_account_dict( + rm_item, inventory_account_map, "supplier_warehouse" + ) + # Supplier Warehouse Account (Credit) self.add_gl_entry( gl_entries=gl_entries, - account=supplier_warehouse_account, + account=_inv_dict.get("account"), cost_center=rm_item.cost_center or item.cost_center, debit=0.0, credit=flt(rm_item.amount), remarks=remarks, against_account=rm_item.expense_account or item.expense_account, - account_currency=get_account_currency(supplier_warehouse_account), + account_currency=_inv_dict.get("account_currency"), project=item.project, item=item, ) @@ -713,7 +715,7 @@ class SubcontractingReceipt(SubcontractingController): debit=flt(rm_item.amount), credit=0.0, remarks=remarks, - against_account=supplier_warehouse_account, + against_account=_inv_dict.get("account"), account_currency=get_account_currency(item.expense_account), project=item.project, item=item, @@ -795,7 +797,7 @@ class SubcontractingReceipt(SubcontractingController): + "\n".join(warehouse_with_no_account) ) - def make_item_gl_entries_for_lcv(self, gl_entries, warehouse_account): + def make_item_gl_entries_for_lcv(self, gl_entries, inventory_account_map): landed_cost_entries = self.get_item_account_wise_lcv_entries() if not landed_cost_entries: @@ -805,6 +807,8 @@ class SubcontractingReceipt(SubcontractingController): if item.landed_cost_voucher_amount and landed_cost_entries: remarks = _("Accounting Entry for Landed Cost Voucher for SCR {0}").format(self.name) if (item.item_code, item.name) in landed_cost_entries: + _inv_dict = self.get_inventory_account_dict(item, inventory_account_map) + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): account_currency = get_account_currency(account) credit_amount = ( @@ -820,7 +824,7 @@ class SubcontractingReceipt(SubcontractingController): debit=0.0, credit=credit_amount, remarks=remarks, - against_account=warehouse_account.get(item.warehouse)["account"], + against_account=_inv_dict["account"], credit_in_account_currency=flt(amount["amount"]), account_currency=account_currency, project=item.project, @@ -837,7 +841,7 @@ class SubcontractingReceipt(SubcontractingController): debit=0.0, credit=credit_amount * -1, remarks=remarks, - against_account=warehouse_account.get(item.warehouse)["account"], + against_account=_inv_dict["account"], debit_in_account_currency=flt(amount["amount"]), account_currency=account_currency, project=item.project,