From 972a990b01dd8b57184eb4cead8e3590899afce7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 05:11:02 +0000 Subject: [PATCH] fix: do not fetch a random inventory account when multiple inventory accounts exist (backport #57626) (#57631) * fix: do not fetch a random inventory account when multiple inventory accounts exist (#57626) (cherry picked from commit 386a4ac1f09d184a9fc39f91c340cb0dc4539d0e) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts Removed redundant test for valuation taxes in purchase receipt. * fix: build warehouse account map only when perpetual inventory needs it For asset purchase receipts or provisional accounting with perpetual inventory disabled, GL entries do not use warehouse accounts. Building the full warehouse account map in that case now throws when a company has multiple inventory accounts and no default, breaking asset receipt submission. Mirrors the gating on develop. Co-Authored-By: Claude Fable 5 * test: set default inventory account in valuation taxes LCV test The conflict resolution kept the pre-backport copy of test_valuation_taxes_lcv_repost_after_billing, which enables perpetual inventory on _Test Company without configuring a default inventory account. The test then failed on submit and leaked the perpetual inventory flag, breaking every stock test that ran after it in the same process. Restore the cherry-picked version from #57626. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: rohitwaghchaure Co-authored-by: Claude Fable 5 --- .../sales_invoice/test_sales_invoice.py | 8 +++- erpnext/controllers/stock_controller.py | 7 +++- erpnext/stock/__init__.py | 7 +++- .../test_landed_cost_voucher.py | 8 +++- .../purchase_receipt/test_purchase_receipt.py | 18 +++++---- .../stock/doctype/warehouse/test_warehouse.py | 38 +++++++++++++++++++ 6 files changed, 73 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0b1f1e922bf..bca0d58a57d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2802,12 +2802,15 @@ class TestSalesInvoice(FrappeTestCase): old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company 1") frappe.local.enable_perpetual_inventory["_Test Company 1"] = 1 + old_inventory_account = frappe.db.get_value("Company", "_Test Company 1", "default_inventory_account") frappe.db.set_value( "Company", "_Test Company 1", - "stock_received_but_not_billed", - "Stock Received But Not Billed - _TC1", + { + "stock_received_but_not_billed": "Stock Received But Not Billed - _TC1", + "default_inventory_account": "Stock In Hand - _TC1", + }, ) frappe.db.set_value( "Company", @@ -2852,6 +2855,7 @@ class TestSalesInvoice(FrappeTestCase): # tear down frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory + frappe.db.set_value("Company", "_Test Company 1", "default_inventory_account", old_inventory_account) frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock) def test_sle_for_target_warehouse(self): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b4218b85f0e..269f85ffcbb 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -177,13 +177,18 @@ class StockController(AccountsController): ) is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items")) + need_inventory_map = (self.get_stock_items() or self.get("packed_items")) and cint( + erpnext.is_perpetual_inventory_enabled(self.company) + ) if ( cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items or is_asset_pr ): - warehouse_account = get_warehouse_account_map(self.company) + warehouse_account = frappe._dict() + if need_inventory_map: + warehouse_account = get_warehouse_account_map(self.company) if self.docstatus == 1: if not gl_entries: diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index 242bdcf8b55..aa556c62434 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -79,10 +79,13 @@ def get_warehouse_account(warehouse, warehouse_account=None): account = get_company_default_inventory_account(warehouse.company) if not account and warehouse.company: - account = frappe.db.get_value( - "Account", {"account_type": "Stock", "is_group": 0, "company": warehouse.company}, "name" + inventory_accounts = frappe.get_all( + "Account", {"account_type": "Stock", "is_group": 0, "company": warehouse.company}, pluck="name" ) + if len(inventory_accounts) == 1: + account = inventory_accounts[0] + if not account and warehouse.company and not warehouse.is_group: frappe.throw( _("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}").format( diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index fd65b7f60e7..3412d818e31 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -194,8 +194,10 @@ class TestLandedCostVoucher(FrappeTestCase): epi = is_perpetual_inventory_enabled(company_a) company_doc = frappe.get_doc("Company", company_a) + old_inventory_account = company_doc.default_inventory_account company_doc.enable_perpetual_inventory = 1 company_doc.stock_received_but_not_billed = srbnb + company_doc.default_inventory_account = "Stock In Hand - _TC" company_doc.save() pr = make_purchase_receipt( @@ -223,7 +225,11 @@ class TestLandedCostVoucher(FrappeTestCase): distribute_landed_cost_on_items(lcv) lcv.submit() - frappe.db.set_value("Company", company_a, "enable_perpetual_inventory", epi) + frappe.db.set_value( + "Company", + company_a, + {"enable_perpetual_inventory": epi, "default_inventory_account": old_inventory_account}, + ) frappe.local.enable_perpetual_inventory = {} def test_landed_cost_voucher_for_zero_purchase_rate(self): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 9d51a20f605..471de86f0a7 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3263,11 +3263,14 @@ class TestPurchaseReceipt(FrappeTestCase): old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company") frappe.local.enable_perpetual_inventory["_Test Company"] = 1 + old_inventory_account = frappe.db.get_value("Company", "_Test Company", "default_inventory_account") frappe.db.set_value( "Company", "_Test Company", - "stock_received_but_not_billed", - "Stock Received But Not Billed - _TC", + { + "stock_received_but_not_billed": "Stock Received But Not Billed - _TC", + "default_inventory_account": "Stock In Hand - _TC", + }, ) pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) @@ -3296,13 +3299,14 @@ class TestPurchaseReceipt(FrappeTestCase): gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False) warehouse_account = get_warehouse_account_map("_Test Company") expected_gle = ( - ("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"), - ("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"), - ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), - (warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"), + ("Stock Received But Not Billed - _TC", 0.0, 10000.0, "Main - _TC"), + ("Freight and Forwarding Charges - _TC", 0.0, 2000.0, "Main - _TC"), + ("Expenses Included In Valuation - _TC", 0.0, 2000.0, "Main - _TC"), + (warehouse_account[pr.items[0].warehouse]["account"], 14000.0, 0.0, "Main - _TC"), ) - self.assertSequenceEqual(expected_gle, gl_entries) + self.assertCountEqual(expected_gle, gl_entries) frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory + frappe.db.set_value("Company", "_Test Company", "default_inventory_account", old_inventory_account) def test_manufacturing_and_expiry_date_for_batch(self): item = make_item( diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 02d64cadfe6..5b6f8f727fb 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -103,6 +103,44 @@ class TestWarehouse(FrappeTestCase): children = get_children("Warehouse", parent=company, company=company, is_root=True) self.assertTrue(any(wh["value"] == "_Test Warehouse - _TC" for wh in children)) + def test_inventory_account_fallback_with_multiple_stock_accounts(self): + from erpnext.stock import get_warehouse_account + + company = create_inventory_fallback_company() + frappe.db.set_value("Company", company, "default_inventory_account", None) + if frappe.db.exists("Account", "Extra Inventory Account - _TCIF"): + frappe.delete_doc("Account", "Extra Inventory Account - _TCIF") + + warehouse = frappe.get_doc("Warehouse", {"company": company, "is_group": 0}) + single_account = frappe.db.get_value( + "Account", {"account_type": "Stock", "is_group": 0, "company": company}, "name" + ) + self.assertEqual(get_warehouse_account(warehouse), single_account) + + create_account( + account_name="Extra Inventory Account", + parent_account=frappe.db.get_value("Account", single_account, "parent_account"), + account_type="Stock", + company=company, + ) + self.assertRaises(frappe.ValidationError, get_warehouse_account, warehouse) + + +def create_inventory_fallback_company(): + company = "_Test Company Inventory Fallback" + if not frappe.db.exists("Company", company): + frappe.get_doc( + { + "doctype": "Company", + "company_name": company, + "abbr": "_TCIF", + "default_currency": "INR", + "enable_perpetual_inventory": 0, + "country": "India", + } + ).insert(ignore_permissions=True) + return company + def create_warehouse(warehouse_name, properties=None, company=None): if not company: