From 2cd759ebafdd084276275ee9f36dfcf33423c1b9 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 30 Jul 2026 19:43:45 +0530 Subject: [PATCH 1/2] fix: do not fetch a random inventory account when multiple inventory accounts exist (#57626) (cherry picked from commit 386a4ac1f09d184a9fc39f91c340cb0dc4539d0e) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 93 +++++++++++++++++++ erpnext/stock/__init__.py | 7 +- .../test_landed_cost_voucher.py | 8 +- .../purchase_receipt/test_purchase_receipt.py | 8 +- .../stock/doctype/warehouse/test_warehouse.py | 38 ++++++++ 5 files changed, 149 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 ea1bc3195fcf..8a4b3a2b42ed 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3064,6 +3064,99 @@ def test_restrict_inter_company_pi_when_sales_invoice_qty_fully_consumed(self): ): make_inter_company_transaction("Sales Invoice", si.name) +<<<<<<< HEAD +======= + def test_inter_company_transaction_does_not_inherit_party_fields(self): + """ + Party-derived fields on SI (from Customer) must not leak into the mapped PI. + """ + si = create_sales_invoice( + company="Wind Power LLC", + customer="_Test Internal Customer", + debit_to="Debtors - WP", + warehouse="Stores - WP", + income_account="Sales - WP", + expense_account="Cost of Goods Sold - WP", + cost_center="Main - WP", + currency="USD", + do_not_save=1, + ) + si.selling_price_list = "_Test Price List Rest of the World" + si.tax_category = "_Test Tax Category 1" + si.language = "ar" + si.payment_terms_template = "_Test Payment Term Template" + si.submit() + + pi = make_inter_company_transaction("Sales Invoice", si.name) + + supplier = frappe.get_doc("Supplier", "_Test Internal Supplier") + self.assertEqual(pi.tax_category or None, supplier.tax_category or None) + self.assertEqual(pi.language or None, supplier.language or None) + self.assertEqual(pi.payment_terms_template or None, supplier.payment_terms or None) + + def test_inter_company_transaction_without_default_warehouse(self): + "Check mapping (expense account) of inter company SI to PI in absence of default warehouse." + # setup + old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) + + 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", + "default_inventory_account": "Stock In Hand - _TC1", + }, + ) + + # companies are created with their Stores warehouse as Default Warehouse; clear it so the + # item genuinely maps without one + frappe.db.set_value("Company", "_Test Company 1", "default_warehouse", None) + + # begin test + si = create_sales_invoice( + company="Wind Power LLC", + customer="_Test Internal Customer", + debit_to="Debtors - WP", + warehouse="Stores - WP", + income_account="Sales - WP", + expense_account="Cost of Goods Sold - WP", + cost_center="Main - WP", + currency="USD", + update_stock=1, + do_not_save=1, + ) + si.selling_price_list = "_Test Price List Rest of the World" + si.submit() + + target_doc = make_inter_company_transaction("Sales Invoice", si.name) + + # in absence of warehouse Stock Received But Not Billed is set as expense account while mapping + # mapping is not obstructed + self.assertIsNone(target_doc.items[0].warehouse) + self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1") + + target_doc.items[0].update({"cost_center": "Main - _TC1"}) + + # missing warehouse is validated on save, after mapping + self.assertRaises(WarehouseMissingError, target_doc.save) + + target_doc.items[0].update({"warehouse": "Stores - _TC1"}) + target_doc.save() + + # after warehouse is set, linked account or default inventory account is set + self.assertEqual(target_doc.items[0].expense_account, "Stock In Hand - _TC1") + + # 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) + +>>>>>>> 386a4ac1f0 (fix: do not fetch a random inventory account when multiple inventory accounts exist (#57626)) def test_sle_for_target_warehouse(self): se = make_stock_entry( item_code="138-CMS Shoe", diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index 734aee54c097..032bf25a1258 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 26df7f591358..a45787635ed0 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 @@ -199,8 +199,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( @@ -228,7 +230,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 b0e6c6f8caca..f7fba10ac5c4 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3134,11 +3134,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) @@ -3174,6 +3177,7 @@ def test_valuation_taxes_lcv_repost_after_billing(self): ) 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_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): batch_item = make_item( diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 5a36d5c213e9..f7bf8670f25b 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -95,6 +95,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 22d158b9c44696f5353ccfb6cf3f48b3e913399d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 30 Jul 2026 22:51:11 +0530 Subject: [PATCH 2/2] chore: fix conflicts Remove redundant inter-company transaction tests and related setup. --- .../sales_invoice/test_sales_invoice.py | 93 ------------------- 1 file changed, 93 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 8a4b3a2b42ed..ea1bc3195fcf 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3064,99 +3064,6 @@ def test_restrict_inter_company_pi_when_sales_invoice_qty_fully_consumed(self): ): make_inter_company_transaction("Sales Invoice", si.name) -<<<<<<< HEAD -======= - def test_inter_company_transaction_does_not_inherit_party_fields(self): - """ - Party-derived fields on SI (from Customer) must not leak into the mapped PI. - """ - si = create_sales_invoice( - company="Wind Power LLC", - customer="_Test Internal Customer", - debit_to="Debtors - WP", - warehouse="Stores - WP", - income_account="Sales - WP", - expense_account="Cost of Goods Sold - WP", - cost_center="Main - WP", - currency="USD", - do_not_save=1, - ) - si.selling_price_list = "_Test Price List Rest of the World" - si.tax_category = "_Test Tax Category 1" - si.language = "ar" - si.payment_terms_template = "_Test Payment Term Template" - si.submit() - - pi = make_inter_company_transaction("Sales Invoice", si.name) - - supplier = frappe.get_doc("Supplier", "_Test Internal Supplier") - self.assertEqual(pi.tax_category or None, supplier.tax_category or None) - self.assertEqual(pi.language or None, supplier.language or None) - self.assertEqual(pi.payment_terms_template or None, supplier.payment_terms or None) - - def test_inter_company_transaction_without_default_warehouse(self): - "Check mapping (expense account) of inter company SI to PI in absence of default warehouse." - # setup - old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") - frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) - - 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", - "default_inventory_account": "Stock In Hand - _TC1", - }, - ) - - # companies are created with their Stores warehouse as Default Warehouse; clear it so the - # item genuinely maps without one - frappe.db.set_value("Company", "_Test Company 1", "default_warehouse", None) - - # begin test - si = create_sales_invoice( - company="Wind Power LLC", - customer="_Test Internal Customer", - debit_to="Debtors - WP", - warehouse="Stores - WP", - income_account="Sales - WP", - expense_account="Cost of Goods Sold - WP", - cost_center="Main - WP", - currency="USD", - update_stock=1, - do_not_save=1, - ) - si.selling_price_list = "_Test Price List Rest of the World" - si.submit() - - target_doc = make_inter_company_transaction("Sales Invoice", si.name) - - # in absence of warehouse Stock Received But Not Billed is set as expense account while mapping - # mapping is not obstructed - self.assertIsNone(target_doc.items[0].warehouse) - self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1") - - target_doc.items[0].update({"cost_center": "Main - _TC1"}) - - # missing warehouse is validated on save, after mapping - self.assertRaises(WarehouseMissingError, target_doc.save) - - target_doc.items[0].update({"warehouse": "Stores - _TC1"}) - target_doc.save() - - # after warehouse is set, linked account or default inventory account is set - self.assertEqual(target_doc.items[0].expense_account, "Stock In Hand - _TC1") - - # 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) - ->>>>>>> 386a4ac1f0 (fix: do not fetch a random inventory account when multiple inventory accounts exist (#57626)) def test_sle_for_target_warehouse(self): se = make_stock_entry( item_code="138-CMS Shoe",