From f7bae888cfdccc706f1b6ce674af886d7ceeaf8f Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sat, 8 Aug 2026 11:01:21 +0530 Subject: [PATCH] fix: incorrect entry detection in Stock Ledger Invariant Check (#57886) (cherry picked from commit b3f97cd38965c08fa10a27216fea03a45cd2684d) --- .../stock_ledger_invariant_check.py | 15 ++++++++-- .../test_stock_ledger_invariant_check.py | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index ffb024acfb1..7827203ae92 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -7,6 +7,8 @@ import frappe from frappe import _ from frappe.utils import cint, flt, get_link_to_form, parse_json +from erpnext.stock.utils import get_valuation_method + SLE_FIELDS = ( "name", "posting_date", @@ -53,6 +55,9 @@ def add_invariant_check_fields(sles, filters): balance_qty = 0.0 balance_stock_value = 0.0 + company = frappe.get_cached_value("Warehouse", filters.warehouse, "company") + valuation_method = get_valuation_method(filters.item_code, company) + incorrect_idx = None float_precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) or 3 currency_precision = ( @@ -90,7 +95,7 @@ def add_invariant_check_fields(sles, filters): ) sle.diff_value_diff = sle.stock_value_from_diff - sle.stock_value - if maintains_fifo_queue(sle): + if maintains_fifo_queue(sle, valuation_method): add_fifo_fields(sle, sles[idx - 1] if idx else None) if incorrect_idx is None and not is_sle_has_correct_data(sle, float_precision, currency_precision): @@ -104,8 +109,10 @@ def add_invariant_check_fields(sles, filters): return sles -def maintains_fifo_queue(sle): - # no queue is maintained for serialized/batchwise-valued stock +def maintains_fifo_queue(sle, valuation_method): + if valuation_method == "Moving Average": + return False + return not ( sle.serial_and_batch_bundle or sle.serial_no or (sle.batch_no and sle.use_batchwise_valuation) ) @@ -138,6 +145,8 @@ def is_sle_has_correct_data(sle, float_precision, currency_precision): return ( flt(sle.difference_in_qty, float_precision) == 0.0 and flt(sle.diff_value_diff, currency_precision) == 0.0 + and flt(sle.fifo_qty_diff, float_precision) == 0.0 + and flt(sle.fifo_value_diff, currency_precision) == 0.0 ) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/test_stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/test_stock_ledger_invariant_check.py index 0f71a8834b2..ae692617a77 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/test_stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/test_stock_ledger_invariant_check.py @@ -1,6 +1,8 @@ # Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt +import json + import frappe from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -59,6 +61,34 @@ class TestStockLedgerInvariantCheck(ERPNextTestSuite): self.assertEqual(len(data), 2) # incorrect entry + one before it for context self.assertEqual(data[-1].name, sle.name) + def test_show_incorrect_entries_catches_queue_mismatch(self): + item = self.make_movements() + + sle = frappe.get_last_doc( + "Stock Ledger Entry", {"item_code": item, "warehouse": WAREHOUSE, "is_cancelled": 0} + ) + tampered_queue = json.dumps([[sle.qty_after_transaction + 5, 100]]) + frappe.db.set_value("Stock Ledger Entry", sle.name, "stock_queue", tampered_queue) + + data = self.run_report(item_code=item, show_incorrect_entries=1) + self.assertEqual(len(data), 2) + self.assertEqual(data[-1].name, sle.name) + + def test_moving_average_item_skips_fifo_queue_checks(self): + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item(properties={"valuation_method": "Moving Average"}).name + make_stock_entry(item_code=item, to_warehouse=WAREHOUSE, qty=10, rate=100) + make_stock_entry(item_code=item, from_warehouse=WAREHOUSE, qty=4) + + data = self.run_report(item_code=item) + self.assertTrue(data) + for row in data: + self.assertIsNone(row.fifo_qty_diff) + self.assertIsNone(row.fifo_value_diff) + + self.assertEqual(self.run_report(item_code=item, show_incorrect_entries=1), []) + def test_batch_item_skips_fifo_queue_checks(self): from erpnext.stock.doctype.item.test_item import make_item