Merge pull request #57256 from mihir-kandoi/fix-multi-batch-serial-stock-reco

fix: scope current serial nos to the selected batch in stock reconciliation
This commit is contained in:
Mihir Kandoi
2026-07-18 18:56:15 +05:30
committed by GitHub
2 changed files with 106 additions and 3 deletions

View File

@@ -1504,18 +1504,17 @@ def get_stock_balance_for(
"use_serial_batch_fields": row.use_serial_batch_fields if row else use_serial_batch_fields,
}
# TODO: fetch only selected batch's values
data = get_stock_balance(
item_code,
warehouse,
posting_date,
posting_time,
with_valuation_rate=with_valuation_rate,
with_serial_no=has_serial_no,
with_serial_no=has_serial_no and not has_batch_no,
inventory_dimensions_dict=inventory_dimensions_dict,
)
if has_serial_no:
if has_serial_no and not has_batch_no:
qty, rate, serial_nos = data
else:
qty, rate = data
@@ -1534,6 +1533,20 @@ def get_stock_balance_for(
or 0
)
if has_serial_no:
serial_no_details = get_available_serial_nos(
frappe._dict(
{
"item_code": item_code,
"warehouse": warehouse,
"posting_datetime": combine_datetime(posting_date, posting_time),
"ignore_warehouse": 1,
"has_batch_no": 1,
}
)
)
serial_nos = "\n".join(d.serial_no for d in serial_no_details if d.batch_no == batch_no)
if row.use_serial_batch_fields and row.batch_no and (qty or row.current_qty):
rate = get_incoming_rate(
frappe._dict(

View File

@@ -1727,6 +1727,96 @@ class TestStockReconciliation(ERPNextTestSuite, StockTestMixin):
self.assertNotEqual(status, "Active")
def test_serial_nos_scoped_to_selected_batch(self):
from erpnext.stock.doctype.batch.batch import get_batch_qty
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import get_stock_balance_for
item_code = self.make_item(
"Test Multi Batch Serial Item Reco",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TMBSIR-BATCH-.###",
"has_serial_no": 1,
"serial_no_series": "TMBSIR-SN-.####",
},
).name
warehouse = "_Test Warehouse - _TC"
batch_wise_serial_nos = {}
for qty in (5, 3):
pr = make_purchase_receipt(
item_code=item_code,
warehouse=warehouse,
qty=qty,
rate=100,
posting_date=add_days(nowdate(), -3),
)
bundle = pr.items[0].serial_and_batch_bundle
batch_wise_serial_nos[get_batch_from_bundle(bundle)] = get_serial_nos_from_bundle(bundle)
first_batch, second_batch = batch_wise_serial_nos
serial_nos = batch_wise_serial_nos[second_batch]
data = get_stock_balance_for(
item_code,
warehouse,
nowdate(),
nowtime(),
batch_no=second_batch,
row={
"use_serial_batch_fields": 1,
"item_code": item_code,
"warehouse": warehouse,
"batch_no": second_batch,
},
company="_Test Company",
)
self.assertEqual(data["qty"], 3)
self.assertEqual(sorted(data["serial_nos"].split("\n")), sorted(serial_nos))
reco = create_stock_reconciliation(
item_code=item_code,
warehouse=warehouse,
qty=2,
rate=100,
batch_no=second_batch,
serial_no="\n".join(serial_nos[:2]),
use_serial_batch_fields=1,
reconcile_all_serial_batch=0,
)
reco.load_from_db()
self.assertEqual(reco.items[0].current_qty, 3)
self.assertEqual(
sorted(get_serial_nos_from_bundle(reco.items[0].current_serial_and_batch_bundle)),
sorted(serial_nos),
)
self.assertEqual(get_batch_qty(second_batch, warehouse, item_code), 2)
self.assertEqual(get_batch_qty(first_batch, warehouse, item_code), 5)
# Backdated fetch: the reco above already pulled serial_nos[2] out of the warehouse,
# but on the day before the reco all three batch serials were still in stock.
data = get_stock_balance_for(
item_code,
warehouse,
add_days(nowdate(), -1),
nowtime(),
batch_no=second_batch,
row={
"use_serial_batch_fields": 1,
"item_code": item_code,
"warehouse": warehouse,
"batch_no": second_batch,
},
company="_Test Company",
)
self.assertEqual(data["qty"], 3)
self.assertEqual(sorted(data["serial_nos"].split("\n")), sorted(serial_nos))
def test_change_valuation_of_batch_using_backdated_stock_reco(self):
from erpnext.stock.doctype.batch.batch import get_batch_qty
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry