mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 16:45:02 +00:00
feat: enable item wise inventory account
This commit is contained in:
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
509
erpnext/controllers/tests/test_item_wise_inventory_account.py
Normal file
509
erpnext/controllers/tests/test_item_wise_inventory_account.py
Normal file
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user