mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-21 05:59:18 +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.buying.utils import check_on_hold_or_closed_status
|
||||||
from erpnext.controllers.accounts_controller import validate_account_head
|
from erpnext.controllers.accounts_controller import validate_account_head
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
from erpnext.stock import get_warehouse_account_map
|
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
update_billed_amount_based_on_po,
|
update_billed_amount_based_on_po,
|
||||||
)
|
)
|
||||||
@@ -460,11 +459,12 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.asset_received_but_not_billed = None
|
self.asset_received_but_not_billed = None
|
||||||
|
|
||||||
|
inventory_account_map = {}
|
||||||
if self.update_stock:
|
if self.update_stock:
|
||||||
self.validate_item_code()
|
self.validate_item_code()
|
||||||
self.validate_warehouse(for_validate)
|
self.validate_warehouse(for_validate)
|
||||||
if auto_accounting_for_stock:
|
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"):
|
for item in self.get("items"):
|
||||||
# in case of auto inventory accounting,
|
# 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 self.update_stock and item.warehouse and (not item.from_warehouse):
|
||||||
if (
|
_inv_dict = self.get_inventory_account_dict(item, inventory_account_map)
|
||||||
for_validate
|
|
||||||
and item.expense_account
|
if for_validate and item.expense_account and item.expense_account != _inv_dict["account"]:
|
||||||
and item.expense_account != warehouse_account[item.warehouse]["account"]
|
|
||||||
):
|
|
||||||
msg = _(
|
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"
|
"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(
|
).format(
|
||||||
item.idx,
|
item.idx,
|
||||||
frappe.bold(warehouse_account[item.warehouse]["account"]),
|
frappe.bold(_inv_dict["account"]),
|
||||||
frappe.bold(item.expense_account),
|
frappe.bold(item.expense_account),
|
||||||
frappe.bold(item.warehouse),
|
frappe.bold(item.warehouse),
|
||||||
)
|
)
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
item.expense_account = _inv_dict["account"]
|
||||||
else:
|
else:
|
||||||
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
||||||
if item.purchase_receipt:
|
if item.purchase_receipt:
|
||||||
@@ -857,7 +855,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
party=self.supplier,
|
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)
|
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||||
|
|
||||||
if self.auto_accounting_for_stock:
|
if self.auto_accounting_for_stock:
|
||||||
@@ -947,7 +945,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# item gl entries
|
# item gl entries
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
if self.update_stock and self.auto_accounting_for_stock:
|
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()
|
landed_cost_entries = self.get_item_account_wise_lcv_entries()
|
||||||
|
|
||||||
@@ -997,18 +995,24 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if item.from_warehouse:
|
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(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": warehouse_account[item.warehouse]["account"],
|
"account": _inv_dict["account"],
|
||||||
"against": warehouse_account[item.from_warehouse]["account"],
|
"against": _inv_dict_from_warehouse["account"],
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"debit": warehouse_debit_amount,
|
"debit": warehouse_debit_amount,
|
||||||
"debit_in_transaction_currency": item.net_amount,
|
"debit_in_transaction_currency": item.net_amount,
|
||||||
},
|
},
|
||||||
warehouse_account[item.warehouse]["account_currency"],
|
_inv_dict["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1021,15 +1025,15 @@ class PurchaseInvoice(BuyingController):
|
|||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": warehouse_account[item.from_warehouse]["account"],
|
"account": _inv_dict_from_warehouse["account"],
|
||||||
"against": warehouse_account[item.warehouse]["account"],
|
"against": _inv_dict["account"],
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
||||||
"debit_in_transaction_currency": item.net_amount,
|
"debit_in_transaction_currency": item.net_amount,
|
||||||
},
|
},
|
||||||
warehouse_account[item.from_warehouse]["account_currency"],
|
_inv_dict_from_warehouse["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1097,15 +1101,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
# sub-contracting warehouse
|
# sub-contracting warehouse
|
||||||
if flt(item.rm_supp_cost):
|
if flt(item.rm_supp_cost):
|
||||||
supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"]
|
supplier_wh_dict = self.get_inventory_account_dict(
|
||||||
if not supplier_warehouse_account:
|
item, inventory_account_map, "supplier_warehouse"
|
||||||
|
)
|
||||||
|
|
||||||
|
supplier_inventory_account = supplier_wh_dict["account"]
|
||||||
|
if not supplier_inventory_account:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Please set account in Warehouse {0}").format(self.supplier_warehouse)
|
_("Please set account in Warehouse {0}").format(self.supplier_warehouse)
|
||||||
)
|
)
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": supplier_warehouse_account,
|
"account": supplier_inventory_account,
|
||||||
"against": item.expense_account,
|
"against": item.expense_account,
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
@@ -1113,7 +1121,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"credit": flt(item.rm_supp_cost),
|
"credit": flt(item.rm_supp_cost),
|
||||||
"credit_in_transaction_currency": item.net_amount,
|
"credit_in_transaction_currency": item.net_amount,
|
||||||
},
|
},
|
||||||
warehouse_account[self.supplier_warehouse]["account_currency"],
|
supplier_wh_dict["account_currency"],
|
||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ class RepostAccountingLedger(Document):
|
|||||||
if doc.doctype in ["Payment Entry", "Journal Entry"]:
|
if doc.doctype in ["Payment Entry", "Journal Entry"]:
|
||||||
gle_map = doc.build_gl_map()
|
gle_map = doc.build_gl_map()
|
||||||
elif doc.doctype == "Purchase Receipt":
|
elif doc.doctype == "Purchase Receipt":
|
||||||
warehouse_account_map = get_warehouse_account_map(doc.company)
|
inventory_account_map = doc.get_inventory_account_map()
|
||||||
gle_map = doc.get_gl_entries(warehouse_account_map)
|
gle_map = doc.get_gl_entries(inventory_account_map)
|
||||||
else:
|
else:
|
||||||
gle_map = doc.get_gl_entries()
|
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):
|
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)
|
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
|
from erpnext.accounts.general_ledger import merge_similar_entries
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|||||||
@@ -1531,7 +1531,8 @@ def repost_gle_for_stock_vouchers(
|
|||||||
voucher_obj = frappe.get_lazy_doc(voucher_type, voucher_no)
|
voucher_obj = frappe.get_lazy_doc(voucher_type, voucher_no)
|
||||||
# Some transactions post credit as negative debit, this is handled while posting GLE
|
# 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
|
# 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 expected_gle:
|
||||||
if not existing_gle or not compare_existing_and_expected_gle(
|
if not existing_gle or not compare_existing_and_expected_gle(
|
||||||
existing_gle, expected_gle, precision
|
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.controllers.stock_controller import StockController
|
||||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
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.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.doctype.item.item import get_item_defaults
|
||||||
from erpnext.stock.get_item_details import (
|
from erpnext.stock.get_item_details import (
|
||||||
ItemDetailsCtx,
|
ItemDetailsCtx,
|
||||||
@@ -412,13 +411,15 @@ class AssetCapitalization(StockController):
|
|||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
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
|
# Stock GL Entries
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
self.warehouse_account = warehouse_account
|
self.inventory_account_map = inventory_account_map
|
||||||
if not self.warehouse_account:
|
if not self.inventory_account_map:
|
||||||
self.warehouse_account = get_warehouse_account_map(self.company)
|
self.inventory_account_map = self.get_inventory_account_map()
|
||||||
|
|
||||||
precision = self.get_debit_field_precision()
|
precision = self.get_debit_field_precision()
|
||||||
self.sle_map = self.get_stock_ledger_details()
|
self.sle_map = self.get_stock_ledger_details()
|
||||||
@@ -457,11 +458,12 @@ class AssetCapitalization(StockController):
|
|||||||
for item_row in self.stock_items:
|
for item_row in self.stock_items:
|
||||||
sle_list = self.sle_map.get(item_row.name)
|
sle_list = self.sle_map.get(item_row.name)
|
||||||
if sle_list:
|
if sle_list:
|
||||||
|
_inv_dict = self.get_inventory_account_dict(item_row, self.inventory_account_map)
|
||||||
for sle in sle_list:
|
for sle in sle_list:
|
||||||
stock_value_difference = flt(sle.stock_value_difference, precision)
|
stock_value_difference = flt(sle.stock_value_difference, precision)
|
||||||
|
|
||||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
account = self.warehouse_account[sle.warehouse]["account"]
|
account = _inv_dict["account"]
|
||||||
else:
|
else:
|
||||||
account = self.get_company_default("default_expense_account")
|
account = self.get_company_default("default_expense_account")
|
||||||
|
|
||||||
@@ -476,7 +478,7 @@ class AssetCapitalization(StockController):
|
|||||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||||
"credit": -1 * stock_value_difference,
|
"credit": -1 * stock_value_difference,
|
||||||
},
|
},
|
||||||
self.warehouse_account[sle.warehouse]["account_currency"],
|
_inv_dict["account_currency"],
|
||||||
item=item_row,
|
item=item_row,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ from erpnext.controllers.sales_and_purchase_return import (
|
|||||||
filter_serial_batches,
|
filter_serial_batches,
|
||||||
make_serial_batch_bundle_for_return,
|
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 import get_warehouse_account_map
|
||||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||||
get_evaluated_inventory_dimension,
|
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 (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
combine_datetime,
|
combine_datetime,
|
||||||
get_type_of_transaction,
|
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):
|
def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
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 provisional_accounting_for_non_stock_items
|
||||||
or is_asset_pr
|
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 self.docstatus == 1:
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
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"
|
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)
|
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||||
|
|
||||||
@@ -578,9 +637,11 @@ class StockController(AccountsController):
|
|||||||
for row in self.items:
|
for row in self.items:
|
||||||
row.use_serial_batch_fields = 1
|
row.use_serial_batch_fields = 1
|
||||||
|
|
||||||
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None):
|
def get_gl_entries(
|
||||||
if not warehouse_account:
|
self, inventory_account_map=None, default_expense_account=None, default_cost_center=None
|
||||||
warehouse_account = get_warehouse_account_map(self.company)
|
):
|
||||||
|
if not inventory_account_map:
|
||||||
|
inventory_account_map = self.get_inventory_account_map()
|
||||||
|
|
||||||
sle_map = self.get_stock_ledger_details()
|
sle_map = self.get_stock_ledger_details()
|
||||||
voucher_details = self.get_voucher_details(default_expense_account, default_cost_center, sle_map)
|
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
|
sle_rounding_diff = 0.0
|
||||||
if sle_list:
|
if sle_list:
|
||||||
for sle in 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
|
# from warehouse account
|
||||||
|
|
||||||
sle_rounding_diff += flt(sle.stock_value_difference)
|
sle_rounding_diff += flt(sle.stock_value_difference)
|
||||||
@@ -602,15 +665,17 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
# expense account/ target_warehouse / source_warehouse
|
# expense account/ target_warehouse / source_warehouse
|
||||||
if item_row.get("target_warehouse"):
|
if item_row.get("target_warehouse"):
|
||||||
warehouse = item_row.get("target_warehouse")
|
_target_wh_inv_dict = self.get_inventory_account_dict(
|
||||||
expense_account = warehouse_account[warehouse]["account"]
|
item_row, inventory_account_map, warehouse_field="target_warehouse"
|
||||||
|
)
|
||||||
|
expense_account = _target_wh_inv_dict["account"]
|
||||||
else:
|
else:
|
||||||
expense_account = item_row.expense_account
|
expense_account = item_row.expense_account
|
||||||
|
|
||||||
gl_list.append(
|
gl_list.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": warehouse_account[sle.warehouse]["account"],
|
"account": _inv_dict["account"],
|
||||||
"against": expense_account,
|
"against": expense_account,
|
||||||
"cost_center": item_row.cost_center,
|
"cost_center": item_row.cost_center,
|
||||||
"project": sle.get("project") or item_row.project or self.get("project"),
|
"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 self.get("is_opening")
|
||||||
or "No",
|
or "No",
|
||||||
},
|
},
|
||||||
warehouse_account[sle.warehouse]["account_currency"],
|
_inv_dict["account_currency"],
|
||||||
item=item_row,
|
item=item_row,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -629,7 +694,7 @@ class StockController(AccountsController):
|
|||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": expense_account,
|
"account": expense_account,
|
||||||
"against": warehouse_account[sle.warehouse]["account"],
|
"against": _inv_dict["account"],
|
||||||
"cost_center": item_row.cost_center,
|
"cost_center": item_row.cost_center,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"debit": -1 * flt(sle.stock_value_difference, precision),
|
"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():
|
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
|
||||||
warehouse_asset_account = ""
|
warehouse_asset_account = ""
|
||||||
if self.get("is_internal_customer"):
|
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"):
|
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")
|
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
|
||||||
if not expense_account:
|
if not expense_account:
|
||||||
@@ -672,7 +743,7 @@ class StockController(AccountsController):
|
|||||||
"debit": sle_rounding_diff,
|
"debit": sle_rounding_diff,
|
||||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
"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,
|
item=item_row,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2015,3 +2086,49 @@ def make_bundle_for_material_transfer(**kwargs):
|
|||||||
bundle_doc.submit()
|
bundle_doc.submit()
|
||||||
|
|
||||||
return bundle_doc.name
|
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",
|
"stock_tab",
|
||||||
"auto_accounting_for_stock_settings",
|
"auto_accounting_for_stock_settings",
|
||||||
"enable_perpetual_inventory",
|
"enable_perpetual_inventory",
|
||||||
|
"enable_item_wise_inventory_account",
|
||||||
"enable_provisional_accounting_for_non_stock_items",
|
"enable_provisional_accounting_for_non_stock_items",
|
||||||
"default_inventory_account",
|
"default_inventory_account",
|
||||||
"stock_adjustment_account",
|
|
||||||
"column_break_32",
|
"column_break_32",
|
||||||
|
"stock_adjustment_account",
|
||||||
"stock_received_but_not_billed",
|
"stock_received_but_not_billed",
|
||||||
"default_provisional_account",
|
"default_provisional_account",
|
||||||
"default_in_transit_warehouse",
|
"default_in_transit_warehouse",
|
||||||
@@ -877,6 +878,13 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Service Expense Account",
|
"label": "Service Expense Account",
|
||||||
"options": "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",
|
"icon": "fa fa-building",
|
||||||
@@ -884,7 +892,7 @@
|
|||||||
"image_field": "company_logo",
|
"image_field": "company_logo",
|
||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-10-10 15:12:37.941251",
|
"modified": "2025-10-23 13:15:52.411984",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Company",
|
"name": "Company",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe import _
|
from frappe import _, bold
|
||||||
from frappe.cache_manager import clear_defaults_cache
|
from frappe.cache_manager import clear_defaults_cache
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
@@ -73,6 +73,7 @@ class Company(NestedSet):
|
|||||||
disposal_account: DF.Link | None
|
disposal_account: DF.Link | None
|
||||||
domain: DF.Data | None
|
domain: DF.Data | None
|
||||||
email: DF.Data | None
|
email: DF.Data | None
|
||||||
|
enable_item_wise_inventory_account: DF.Check
|
||||||
enable_perpetual_inventory: DF.Check
|
enable_perpetual_inventory: DF.Check
|
||||||
enable_provisional_accounting_for_non_stock_items: DF.Check
|
enable_provisional_accounting_for_non_stock_items: DF.Check
|
||||||
exception_budget_approver_role: DF.Link | None
|
exception_budget_approver_role: DF.Link | None
|
||||||
@@ -158,6 +159,24 @@ class Company(NestedSet):
|
|||||||
self.set_chart_of_accounts()
|
self.set_chart_of_accounts()
|
||||||
self.validate_parent_company()
|
self.validate_parent_company()
|
||||||
self.set_reporting_currency()
|
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):
|
def validate_abbr(self):
|
||||||
if not self.abbr:
|
if not self.abbr:
|
||||||
@@ -455,6 +474,22 @@ class Company(NestedSet):
|
|||||||
_("Set default inventory account for perpetual inventory"), alert=True, indicator="orange"
|
_("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):
|
def validate_provisional_account_for_non_stock_items(self):
|
||||||
if not self.get("__islocal"):
|
if not self.get("__islocal"):
|
||||||
if (
|
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) {
|
make_dashboard: function (frm) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
"column_break_3",
|
"column_break_3",
|
||||||
"default_price_list",
|
"default_price_list",
|
||||||
"default_discount_account",
|
"default_discount_account",
|
||||||
|
"default_inventory_account",
|
||||||
|
"inventory_account_currency",
|
||||||
"purchase_defaults",
|
"purchase_defaults",
|
||||||
"buying_cost_center",
|
"buying_cost_center",
|
||||||
"default_supplier",
|
"default_supplier",
|
||||||
@@ -168,11 +170,25 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Purchase Expense Contra Account",
|
"label": "Purchase Expense Contra Account",
|
||||||
"options": "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,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-10-01 19:17:33.687836",
|
"modified": "2025-10-21 10:50:46.144721",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Default",
|
"name": "Item Default",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class ItemDefault(Document):
|
|||||||
company: DF.Link
|
company: DF.Link
|
||||||
default_cogs_account: DF.Link | None
|
default_cogs_account: DF.Link | None
|
||||||
default_discount_account: DF.Link | None
|
default_discount_account: DF.Link | None
|
||||||
|
default_inventory_account: DF.Link | None
|
||||||
default_price_list: DF.Link | None
|
default_price_list: DF.Link | None
|
||||||
default_provisional_account: DF.Link | None
|
default_provisional_account: DF.Link | None
|
||||||
default_supplier: DF.Link | None
|
default_supplier: DF.Link | None
|
||||||
@@ -26,6 +27,7 @@ class ItemDefault(Document):
|
|||||||
deferred_revenue_account: DF.Link | None
|
deferred_revenue_account: DF.Link | None
|
||||||
expense_account: DF.Link | None
|
expense_account: DF.Link | None
|
||||||
income_account: DF.Link | None
|
income_account: DF.Link | None
|
||||||
|
inventory_account_currency: DF.Link | None
|
||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
|||||||
@@ -472,19 +472,19 @@ class PurchaseReceipt(BuyingController):
|
|||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.amount_difference_with_purchase_invoice = 0
|
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
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
|
|
||||||
gl_entries = []
|
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.make_tax_gl_entries(gl_entries, via_landed_cost_voucher)
|
||||||
self.set_gl_entry_for_purchase_expense(gl_entries)
|
self.set_gl_entry_for_purchase_expense(gl_entries)
|
||||||
update_regional_gl_entries(gl_entries, self)
|
update_regional_gl_entries(gl_entries, self)
|
||||||
|
|
||||||
return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation)
|
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 (
|
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
|
||||||
get_purchase_document_details,
|
get_purchase_document_details,
|
||||||
)
|
)
|
||||||
@@ -526,9 +526,11 @@ class PurchaseReceipt(BuyingController):
|
|||||||
):
|
):
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
account = (
|
account = stock_asset_rbnb
|
||||||
warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else 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)
|
account_currency = get_account_currency(account)
|
||||||
|
|
||||||
# GL Entry for from warehouse or Stock Received but not billed
|
# 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):
|
def make_sub_contracting_gl_entries(item):
|
||||||
# sub-contracting warehouse
|
# 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(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=supplier_warehouse_account,
|
account=supplier_warehouse_account,
|
||||||
@@ -748,22 +750,25 @@ class PurchaseReceipt(BuyingController):
|
|||||||
stock_value_diff = (
|
stock_value_diff = (
|
||||||
flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount)
|
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_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
|
||||||
stock_asset_account_name = warehouse_account[d.warehouse]["account"]
|
stock_asset_account_name = inventory_account["account"]
|
||||||
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get(
|
|
||||||
"account"
|
supplier_warehouse_account = None
|
||||||
)
|
supplier_warehouse_account_currency = None
|
||||||
supplier_warehouse_account_currency = warehouse_account.get(
|
if self.supplier_warehouse:
|
||||||
self.supplier_warehouse, {}
|
if _inv_dict := self.get_inventory_account_dict(
|
||||||
).get("account_currency")
|
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
|
# If PR is sub-contracted and fg item rate is zero
|
||||||
# in that case if account for source and target warehouse are same,
|
# in that case if account for source and target warehouse are same,
|
||||||
# then GL entries should not be posted
|
# then GL entries should not be posted
|
||||||
if (
|
if (
|
||||||
flt(stock_value_diff) == flt(d.rm_supp_cost)
|
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
|
and stock_asset_account_name == supplier_warehouse_account
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
@@ -795,7 +800,9 @@ class PurchaseReceipt(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
stock_value_diff = get_stock_value_difference(self.name, d.name, d.rejected_warehouse)
|
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)
|
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
||||||
if not d.qty:
|
if not d.qty:
|
||||||
|
|||||||
@@ -1150,7 +1150,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-10-14 12:58:20.384056",
|
"modified": "2025-10-21 10:39:32.659933",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
|||||||
@@ -1644,8 +1644,8 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
|
|
||||||
sl_entries.append(sle)
|
sl_entries.append(sle)
|
||||||
|
|
||||||
def get_gl_entries(self, warehouse_account):
|
def get_gl_entries(self, inventory_account_map):
|
||||||
gl_entries = super().get_gl_entries(warehouse_account)
|
gl_entries = super().get_gl_entries(inventory_account_map)
|
||||||
|
|
||||||
if self.purpose in ("Repack", "Manufacture"):
|
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)
|
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)
|
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()
|
landed_cost_entries = self.get_item_account_wise_lcv_entries()
|
||||||
if not landed_cost_entries:
|
if not landed_cost_entries:
|
||||||
return
|
return
|
||||||
@@ -1742,11 +1742,12 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
else flt(amount["amount"])
|
else flt(amount["amount"])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_inv_dict = self.get_inventory_account_dict(item, inventory_account_map, "t_warehouse")
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": account,
|
"account": account,
|
||||||
"against": warehouse_account.get(item.t_warehouse)["account"],
|
"against": _inv_dict["account"],
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"debit": 0.0,
|
"debit": 0.0,
|
||||||
"credit": credit_amount,
|
"credit": credit_amount,
|
||||||
@@ -1766,7 +1767,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": item.expense_account,
|
"account": item.expense_account,
|
||||||
"against": warehouse_account.get(item.t_warehouse)["account"],
|
"against": _inv_dict["account"],
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"debit": 0.0,
|
"debit": 0.0,
|
||||||
"credit": credit_amount * -1,
|
"credit": credit_amount * -1,
|
||||||
|
|||||||
@@ -944,11 +944,11 @@ class StockReconciliation(StockController):
|
|||||||
new_sl_entries.extend(merge_similar_entries.values())
|
new_sl_entries.extend(merge_similar_entries.values())
|
||||||
return new_sl_entries
|
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:
|
if not self.cost_center:
|
||||||
msgprint(_("Please enter Cost Center"), raise_exception=1)
|
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):
|
def validate_expense_account(self):
|
||||||
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||||
|
|||||||
@@ -545,7 +545,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-06 02:39:24.284587",
|
"modified": "2025-10-23 13:16:10.527190",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Settings",
|
"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):
|
def get_default_expense_account(ctx: ItemDetailsCtx, item, item_group, brand):
|
||||||
if ctx.get("doctype") in ["Sales Invoice", "Delivery Note"]:
|
if ctx.get("doctype") in ["Sales Invoice", "Delivery Note"]:
|
||||||
expense_account = (
|
expense_account = (
|
||||||
|
|||||||
@@ -596,19 +596,19 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
"Subcontracting Receipt", self.name, "status", status, update_modified=update_modified
|
"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
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
|
|
||||||
if not erpnext.is_perpetual_inventory_enabled(self.company):
|
if not erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
self.make_item_gl_entries(gl_entries, warehouse_account)
|
self.make_item_gl_entries(gl_entries, inventory_account_map)
|
||||||
self.make_item_gl_entries_for_lcv(gl_entries, warehouse_account)
|
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)
|
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 = []
|
warehouse_with_no_account = []
|
||||||
|
|
||||||
supplied_items_details = frappe._dict()
|
supplied_items_details = frappe._dict()
|
||||||
@@ -625,7 +625,9 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
|
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if flt(item.rate) and flt(item.qty):
|
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_value_diff = frappe.db.get_value(
|
||||||
"Stock Ledger Entry",
|
"Stock Ledger Entry",
|
||||||
{
|
{
|
||||||
@@ -638,22 +640,18 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
"stock_value_difference",
|
"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")
|
remarks = self.get("remarks") or _("Accounting Entry for Stock")
|
||||||
|
|
||||||
# Accepted Warehouse Account (Debit)
|
# Accepted Warehouse Account (Debit)
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=accepted_warehouse_account,
|
account=_inv_dict["account"],
|
||||||
cost_center=item.cost_center,
|
cost_center=item.cost_center,
|
||||||
debit=stock_value_diff,
|
debit=stock_value_diff,
|
||||||
credit=0.0,
|
credit=0.0,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=item.expense_account,
|
against_account=item.expense_account,
|
||||||
account_currency=get_account_currency(accepted_warehouse_account),
|
account_currency=_inv_dict["account_currency"],
|
||||||
project=item.project,
|
project=item.project,
|
||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
@@ -669,7 +667,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=flt(stock_value_diff) - service_cost,
|
credit=flt(stock_value_diff) - service_cost,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=accepted_warehouse_account,
|
against_account=_inv_dict["account"],
|
||||||
account_currency=get_account_currency(item.expense_account),
|
account_currency=get_account_currency(item.expense_account),
|
||||||
project=item.project,
|
project=item.project,
|
||||||
item=item,
|
item=item,
|
||||||
@@ -684,24 +682,28 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=service_cost,
|
credit=service_cost,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=accepted_warehouse_account,
|
against_account=_inv_dict["account"],
|
||||||
account_currency=get_account_currency(service_account),
|
account_currency=get_account_currency(service_account),
|
||||||
project=item.project,
|
project=item.project,
|
||||||
item=item,
|
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):
|
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)
|
# Supplier Warehouse Account (Credit)
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=supplier_warehouse_account,
|
account=_inv_dict.get("account"),
|
||||||
cost_center=rm_item.cost_center or item.cost_center,
|
cost_center=rm_item.cost_center or item.cost_center,
|
||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=flt(rm_item.amount),
|
credit=flt(rm_item.amount),
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=rm_item.expense_account or item.expense_account,
|
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,
|
project=item.project,
|
||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
@@ -713,7 +715,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
debit=flt(rm_item.amount),
|
debit=flt(rm_item.amount),
|
||||||
credit=0.0,
|
credit=0.0,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=supplier_warehouse_account,
|
against_account=_inv_dict.get("account"),
|
||||||
account_currency=get_account_currency(item.expense_account),
|
account_currency=get_account_currency(item.expense_account),
|
||||||
project=item.project,
|
project=item.project,
|
||||||
item=item,
|
item=item,
|
||||||
@@ -795,7 +797,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
+ "\n".join(warehouse_with_no_account)
|
+ "\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()
|
landed_cost_entries = self.get_item_account_wise_lcv_entries()
|
||||||
|
|
||||||
if not landed_cost_entries:
|
if not landed_cost_entries:
|
||||||
@@ -805,6 +807,8 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
if item.landed_cost_voucher_amount and landed_cost_entries:
|
if item.landed_cost_voucher_amount and landed_cost_entries:
|
||||||
remarks = _("Accounting Entry for Landed Cost Voucher for SCR {0}").format(self.name)
|
remarks = _("Accounting Entry for Landed Cost Voucher for SCR {0}").format(self.name)
|
||||||
if (item.item_code, item.name) in landed_cost_entries:
|
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():
|
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
|
||||||
account_currency = get_account_currency(account)
|
account_currency = get_account_currency(account)
|
||||||
credit_amount = (
|
credit_amount = (
|
||||||
@@ -820,7 +824,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=credit_amount,
|
credit=credit_amount,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=warehouse_account.get(item.warehouse)["account"],
|
against_account=_inv_dict["account"],
|
||||||
credit_in_account_currency=flt(amount["amount"]),
|
credit_in_account_currency=flt(amount["amount"]),
|
||||||
account_currency=account_currency,
|
account_currency=account_currency,
|
||||||
project=item.project,
|
project=item.project,
|
||||||
@@ -837,7 +841,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=credit_amount * -1,
|
credit=credit_amount * -1,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=warehouse_account.get(item.warehouse)["account"],
|
against_account=_inv_dict["account"],
|
||||||
debit_in_account_currency=flt(amount["amount"]),
|
debit_in_account_currency=flt(amount["amount"]),
|
||||||
account_currency=account_currency,
|
account_currency=account_currency,
|
||||||
project=item.project,
|
project=item.project,
|
||||||
|
|||||||
Reference in New Issue
Block a user