feat: enable item wise inventory account

This commit is contained in:
Rohit Waghchaure
2025-10-22 11:20:51 +05:30
parent 5b6979c700
commit 74192547ce
19 changed files with 831 additions and 98 deletions

View File

@@ -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