From 4826b25cb7b795994ce7633efddd27818c996fa7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 30 Jul 2026 19:43:45 +0530 Subject: [PATCH 1/4] 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 --- .../sales_invoice/test_sales_invoice.py | 8 ++- erpnext/stock/__init__.py | 7 ++- .../test_landed_cost_voucher.py | 8 ++- .../purchase_receipt/test_purchase_receipt.py | 55 +++++++++++++++++++ .../stock/doctype/warehouse/test_warehouse.py | 38 +++++++++++++ 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0b1f1e922bf4..bca0d58a57d9 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 @@ def test_inter_company_transaction_without_default_warehouse(self): 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 @@ def test_inter_company_transaction_without_default_warehouse(self): # 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/stock/__init__.py b/erpnext/stock/__init__.py index 242bdcf8b550..aa556c62434d 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 fd65b7f60e77..3412d818e311 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 @@ def test_lcv_validates_company(self): 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 @@ def test_lcv_validates_company(self): 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 9d51a20f605a..4352a3750b32 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2769,6 +2769,61 @@ def test_pr_billed_amount_against_return_entry(self): pr.reload() self.assertEqual(pr.per_billed, 100) +<<<<<<< HEAD +======= + def test_valuation_taxes_lcv_repost_after_billing(self): + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + make_landed_cost_voucher, + ) + + 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", + "default_inventory_account": "Stock In Hand - _TC", + }, + ) + + pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) + pr.append( + "taxes", + { + "category": "Valuation and Total", + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "tax_amount": 2000, + "description": "Test", + }, + ) + pr.submit() + pi = make_purchase_invoice(pr.name) + pi.submit() + make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=2000, + distribute_charges_based_on="Qty", + expense_account="Expenses Included In Valuation - _TC", + ) + + 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.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.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) + +>>>>>>> 386a4ac1f0 (fix: do not fetch a random inventory account when multiple inventory accounts exist (#57626)) def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): batch_item = make_item( "_Test Purchase Receipt Batch Item For Rejected Qty", diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 02d64cadfe6a..5b6f8f727fb6 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -103,6 +103,44 @@ def test_get_children(self): 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: From cf5c2532b4443cc3c74448b6f879437a231ffae6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 30 Jul 2026 22:52:02 +0530 Subject: [PATCH 2/4] chore: fix conflicts Removed redundant test for valuation taxes in purchase receipt. --- .../purchase_receipt/test_purchase_receipt.py | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 4352a3750b32..9d51a20f605a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2769,61 +2769,6 @@ def test_pr_billed_amount_against_return_entry(self): pr.reload() self.assertEqual(pr.per_billed, 100) -<<<<<<< HEAD -======= - def test_valuation_taxes_lcv_repost_after_billing(self): - from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( - make_landed_cost_voucher, - ) - - 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", - "default_inventory_account": "Stock In Hand - _TC", - }, - ) - - pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) - pr.append( - "taxes", - { - "category": "Valuation and Total", - "charge_type": "Actual", - "account_head": "Freight and Forwarding Charges - _TC", - "tax_amount": 2000, - "description": "Test", - }, - ) - pr.submit() - pi = make_purchase_invoice(pr.name) - pi.submit() - make_landed_cost_voucher( - company=pr.company, - receipt_document_type="Purchase Receipt", - receipt_document=pr.name, - charges=2000, - distribute_charges_based_on="Qty", - expense_account="Expenses Included In Valuation - _TC", - ) - - 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.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.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) - ->>>>>>> 386a4ac1f0 (fix: do not fetch a random inventory account when multiple inventory accounts exist (#57626)) def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): batch_item = make_item( "_Test Purchase Receipt Batch Item For Rejected Qty", From c918dcfc0e225357d7a53130a760ab9ef2cba047 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 31 Jul 2026 10:21:51 +0530 Subject: [PATCH 3/4] 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 --- erpnext/controllers/stock_controller.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b4218b85f0e0..269f85ffcbbf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -177,13 +177,18 @@ def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_vo ) 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: From 400a514fc65b9d40ea8945d32c9c57345cb03e6e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 31 Jul 2026 10:22:02 +0530 Subject: [PATCH 4/4] 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 --- .../purchase_receipt/test_purchase_receipt.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 9d51a20f605a..471de86f0a70 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 @@ def test_valuation_taxes_lcv_repost_after_billing(self): 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 @@ def test_valuation_taxes_lcv_repost_after_billing(self): 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(