fix: skip serial nos with no stock when reconciling specific serial nos (backport #59266) (#59267)

This commit is contained in:
Mihir Kandoi
2026-09-22 15:23:16 +05:30
committed by GitHub
parent 814d7f0c4e
commit aaaba7cd82
2 changed files with 77 additions and 16 deletions

View File

@@ -15,6 +15,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_in
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
combine_datetime,
get_available_serial_nos,
get_serial_nos_based_on_posting_date,
)
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
@@ -367,37 +368,64 @@ class StockReconciliation(StockController):
reco_obj = cls_obj.duplicate_package()
total_current_qty = 0.0
entries_in_stock = []
serial_nos_in_stock = self.get_serial_nos_in_stock(row, reco_obj.entries)
for entry in reco_obj.entries:
if not entry.batch_no or entry.serial_no:
total_current_qty += entry.qty
entry.qty *= -1
continue
if entry.serial_no not in serial_nos_in_stock:
continue
current_qty = get_batch_qty(
entry.batch_no,
row.warehouse,
row.item_code,
ignore_voucher_nos=[self.name],
posting_date=self.posting_date,
posting_time=self.posting_time,
for_stock_levels=True,
consider_negative_batches=True,
do_not_check_future_batches=True,
)
current_qty = entry.qty
else:
current_qty = get_batch_qty(
entry.batch_no,
row.warehouse,
row.item_code,
ignore_voucher_nos=[self.name],
posting_date=self.posting_date,
posting_time=self.posting_time,
for_stock_levels=True,
consider_negative_batches=True,
do_not_check_future_batches=True,
)
if not current_qty:
continue
if not current_qty:
continue
total_current_qty += current_qty
entry.qty = current_qty * -1
entries_in_stock.append(entry)
if total_current_qty:
reco_obj.set("entries", entries_in_stock)
reco_obj.save()
row.current_qty = total_current_qty
return reco_obj
def get_serial_nos_in_stock(self, row, entries) -> set:
serial_nos = [entry.serial_no for entry in entries if entry.serial_no]
if not serial_nos:
return set()
in_stock = get_serial_nos_based_on_posting_date(
frappe._dict(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_datetime": combine_datetime(self.posting_date, self.posting_time),
"serial_nos": serial_nos,
"check_serial_nos": True,
"voucher_no": self.name,
}
),
[],
)
return set(in_stock)
def has_change_in_serial_batch(self, row) -> bool:
bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []}

View File

@@ -1326,6 +1326,39 @@ class TestStockReconciliation(ERPNextTestSuite, StockTestMixin):
self.assertAlmostEqual(row.incoming_rate, 1000.00)
self.assertEqual(row.serial_no, serial_nos[row.idx - 1])
def test_opening_stock_reco_for_serial_nos_without_stock(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import make_serial_nos
item = self.make_item(
"Test Serial No Item Opening Stock Not Reconcile All",
{
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "SNN-TEST-OPENING-NRALL-S-.###",
},
)
warehouse = "_Test Warehouse - _TC"
serial_nos = [f"SNN-TEST-OPENING-NRALL-{idx}" for idx in range(1, 6)]
make_serial_nos(item.name, [{"serial_no": serial_no} for serial_no in serial_nos])
with self.change_settings("Stock Settings", {"allow_negative_stock": 0}):
sr = create_stock_reconciliation(
item_code=item.name,
warehouse=warehouse,
qty=5,
rate=100,
purpose="Opening Stock",
expense_account="Temporary Opening - _TC",
reconcile_all_serial_batch=0,
serial_no=serial_nos,
)
self.assertEqual(sr.docstatus, 1)
self.assertEqual(sr.items[0].current_qty, 0)
self.assertFalse(sr.items[0].current_serial_and_batch_bundle)
self.assertEqual(get_stock_balance(item.name, warehouse), 5)
def test_stock_reco_with_legacy_batch(self):
from erpnext.stock.doctype.batch.batch import get_batch_qty