From d09c04a6230e9b15ce7f7ad21603029aff876fa3 Mon Sep 17 00:00:00 2001 From: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:10:15 +0530 Subject: [PATCH] fix: update stock variance account logic which defaults to default expense (#57656) * fix(stock): update stock variance account logic which defaults to default expense account set in company * test: add regression test for purchase invoice stock adjustment account fallback --------- Co-authored-by: Afsal Syed --- .../purchase_invoice/purchase_invoice.py | 30 ++++++- .../purchase_invoice/test_purchase_invoice.py | 90 +++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index b6433e5baf7..8d91408a002 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1380,7 +1380,20 @@ class PurchaseInvoice(BuyingController): ) if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision): - cost_of_goods_sold_account = self.get_company_default("default_expense_account") + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed", ignore_validation=True) + if item.is_fixed_asset + else self.get_company_default("stock_received_but_not_billed", ignore_validation=True) + ) + fallback_account = ( + (item.expense_account or stock_asset_rbnb) + if self.is_return + else (stock_asset_rbnb or item.expense_account) + ) + cost_of_goods_sold_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or fallback_account + ) stock_adjustment_amt = stock_amount - warehouse_debit_amount gl_entries.append( @@ -1405,7 +1418,20 @@ class PurchaseInvoice(BuyingController): and warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision) ): - cost_of_goods_sold_account = self.get_company_default("default_expense_account") + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed", ignore_validation=True) + if item.is_fixed_asset + else self.get_company_default("stock_received_but_not_billed", ignore_validation=True) + ) + fallback_account = ( + (item.expense_account or stock_asset_rbnb) + if self.is_return + else (stock_asset_rbnb or item.expense_account) + ) + cost_of_goods_sold_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or fallback_account + ) stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision) stock_adjustment_amt = warehouse_debit_amount - stock_amount diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 5c2eabe8dcd..f0952bf3307 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1490,6 +1490,96 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): ) frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) + def test_stock_adjustment_account_fallbacks_when_default_expense_account_unset(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import PurchaseInvoice + + class StockAdjustmentInvoice: + company = "_Test Company" + conversion_rate = 1 + update_stock = 1 + is_internal_supplier = 0 + return_against = None + project = None + + def __init__(self, is_return, defaults): + self.is_return = is_return + self.defaults = defaults + + def get(self, fieldname): + return None + + def get_company_default(self, fieldname, ignore_validation=False): + return self.defaults.get(fieldname) + + def get_gl_dict(self, args, *unused_args, **unused_kwargs): + return frappe._dict(args) + + def make_invoice(is_return, defaults): + return StockAdjustmentInvoice(is_return, defaults) + + def make_item(is_fixed_asset=0, expense_account="Item Expense - _TC"): + return frappe._dict( + { + "name": "row-1", + "warehouse": "Stores - _TC", + "valuation_rate": 10, + "qty": 10, + "conversion_factor": 1, + "base_net_amount": 100, + "item_tax_amount": 0, + "landed_cost_voucher_amount": 0, + "sales_incoming_rate": 0, + "is_fixed_asset": is_fixed_asset, + "expense_account": expense_account, + "cost_center": "Main - _TC", + "project": None, + "precision": lambda fieldname: 2, + } + ) + + defaults = { + "default_expense_account": None, + "stock_received_but_not_billed": "Stock Received But Not Billed - _TC", + "asset_received_but_not_billed": "Asset Received But Not Billed - _TC", + } + test_cases = ( + ( + "company default expense", + 0, + make_item(), + {**defaults, "default_expense_account": "Default Expense - _TC"}, + "Default Expense - _TC", + ), + ("stock rbnb", 0, make_item(), defaults, "Stock Received But Not Billed - _TC"), + ( + "asset rbnb", + 0, + make_item(is_fixed_asset=1), + defaults, + "Asset Received But Not Billed - _TC", + ), + ("return item expense", 1, make_item(), defaults, "Item Expense - _TC"), + ( + "return without item expense", + 1, + make_item(expense_account=None), + defaults, + "Stock Received But Not Billed - _TC", + ), + ) + + for label, is_return, item, company_defaults, expected_account in test_cases: + with self.subTest(label=label): + invoice = make_invoice(is_return, company_defaults) + gl_entries = [] + PurchaseInvoice.make_stock_adjustment_entry( + invoice, gl_entries, item, {(item.name, item.warehouse): 90}, "INR" + ) + + self.assertEqual(gl_entries[0].account, expected_account) + self.assertEqual(gl_entries[0].debit, 10) + self.assertEqual(gl_entries[0].debit_in_transaction_currency, 10) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_purchase_invoice_advance_taxes(self): from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry