diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e63a6334829..6bc49503012 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -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( diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 1cda3f0730f..0054a7bdf52 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -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