fix: stock reco test case for serial and batch bundle
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index c537143..b3e0954 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -88,7 +88,6 @@
 		self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
 
 	def test_serialized_item_consumption(self):
-		from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
 
 		stock_entry = make_serialized_item()
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 6d3af42..217f568 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -53,7 +53,6 @@
 				fieldtype: 'Data',
 				fieldname: 'scan_serial_no',
 				label: __('Scan Serial No'),
-				options: 'Serial No',
 				get_query: () => {
 					return {
 						filters: this.get_serial_no_filters()
@@ -71,10 +70,9 @@
 
 		if (this.item.has_batch_no) {
 			fields.push({
-				fieldtype: 'Link',
+				fieldtype: 'Data',
 				fieldname: 'scan_batch_no',
 				label: __('Scan Batch No'),
-				options: 'Batch',
 				get_query: () => {
 					return {
 						filters: {
@@ -104,6 +102,8 @@
 
 		if (this.item?.outward) {
 			fields = [...this.get_filter_fields(), ...fields];
+		} else {
+			fields = [...fields, ...this.get_attach_field()];
 		}
 
 		fields.push({
@@ -121,6 +121,73 @@
 		return fields;
 	}
 
+	get_attach_field() {
+		let label = this.item?.has_serial_no ? __('Serial Nos') : __('Batch Nos');
+		let primary_label = this.bundle
+			? __('Update') : __('Add');
+
+		if (this.item?.has_serial_no && this.item?.has_batch_no) {
+			label = __('Serial Nos / Batch Nos');
+		}
+
+		return [
+			{
+				fieldtype: 'Section Break',
+				label: __('{0} {1} via CSV File', [primary_label, label])
+			},
+			{
+				fieldtype: 'Button',
+				fieldname: 'download_csv',
+				label: __('Download CSV Template'),
+				click: () => this.download_csv_file()
+			},
+			{
+				fieldtype: 'Column Break',
+			},
+			{
+				fieldtype: 'Attach',
+				fieldname: 'attach_serial_batch_csv',
+				label: __('Attach CSV File'),
+				onchange: () => this.upload_csv_file()
+			}
+		]
+	}
+
+	download_csv_file() {
+		let csvFileData = ['Serial No'];
+
+		if (this.item.has_serial_no && this.item.has_batch_no) {
+			csvFileData = ['Serial No', 'Batch No', 'Quantity'];
+		} else if (this.item.has_batch_no) {
+			csvFileData = ['Batch No', 'Quantity'];
+		}
+
+		const method = `/api/method/erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.download_blank_csv_template?content=${encodeURIComponent(JSON.stringify(csvFileData))}`;
+		const w = window.open(frappe.urllib.get_full_url(method));
+		if (!w) {
+			frappe.msgprint(__("Please enable pop-ups"));
+		}
+	}
+
+	upload_csv_file() {
+		const file_path = this.dialog.get_value("attach_serial_batch_csv")
+
+		frappe.call({
+			method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.upload_csv_file',
+			args: {
+				item_code: this.item.item_code,
+				file_path: file_path
+			},
+			callback: (r) => {
+				if (r.message.serial_nos && r.message.serial_nos.length) {
+					this.set_data(r.message.serial_nos);
+				} else if (r.message.batch_nos && r.message.batch_nos.length) {
+					this.set_data(r.message.batch_nos);
+				}
+			}
+		});
+	}
+
 	get_filter_fields() {
 		return [
 			{
@@ -213,10 +280,6 @@
 	get_auto_data() {
 		const { qty, based_on } = this.dialog.get_values();
 
-		if (!qty) {
-			frappe.throw(__('Please enter Qty to Fetch'));
-		}
-
 		if (!based_on) {
 			based_on = 'FIFO';
 		}
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 3edcbe0..98987ae 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -168,7 +168,12 @@
 
 @frappe.whitelist()
 def get_batch_qty(
-	batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None
+	batch_no=None,
+	warehouse=None,
+	item_code=None,
+	posting_date=None,
+	posting_time=None,
+	ignore_voucher_nos=None,
 ):
 	"""Returns batch actual qty if warehouse is passed,
 	        or returns dict of qty by warehouse if warehouse is None
@@ -191,6 +196,7 @@
 			"posting_date": posting_date,
 			"posting_time": posting_time,
 			"batch_no": batch_no,
+			"ignore_voucher_nos": ignore_voucher_nos,
 		}
 	)
 
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index f787caa..ce5801f 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -21,6 +21,7 @@
 	parse_json,
 	today,
 )
+from frappe.utils.csvutils import build_csv_response
 
 from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
 from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
@@ -152,15 +153,15 @@
 		if self.has_serial_no:
 			sn_obj = SerialNoValuation(
 				sle=sle,
-				warehouse=self.item_code,
-				item_code=self.warehouse,
+				item_code=self.item_code,
+				warehouse=self.warehouse,
 			)
 
 		else:
 			sn_obj = BatchNoValuation(
 				sle=sle,
-				warehouse=self.item_code,
-				item_code=self.warehouse,
+				item_code=self.item_code,
+				warehouse=self.warehouse,
 			)
 
 		for d in self.entries:
@@ -657,6 +658,31 @@
 			self.set("entries", batch_nos)
 
 
+@frappe.whitelist()
+def download_blank_csv_template(content):
+	csv_data = []
+	if isinstance(content, str):
+		content = parse_json(content)
+
+	csv_data.append(content)
+	csv_data.append([])
+	csv_data.append([])
+
+	filename = "serial_and_batch_bundle"
+	build_csv_response(csv_data, filename)
+
+
+@frappe.whitelist()
+def upload_csv_file(item_code, file_path):
+	serial_nos, batch_nos = [], []
+	serial_nos, batch_nos = get_serial_batch_from_csv(item_code, file_path)
+
+	return {
+		"serial_nos": serial_nos,
+		"batch_nos": batch_nos,
+	}
+
+
 def get_serial_batch_from_csv(item_code, file_path):
 	file_path = frappe.get_site_path() + file_path
 	serial_nos = []
@@ -669,7 +695,6 @@
 	if serial_nos:
 		make_serial_nos(item_code, serial_nos)
 
-	print(batch_nos)
 	if batch_nos:
 		make_batch_nos(item_code, batch_nos)
 
@@ -938,7 +963,7 @@
 		doc.append(
 			"entries",
 			{
-				"qty": 1 if doc.type_of_transaction == "Inward" else -1,
+				"qty": d.get("qty") * (1 if doc.type_of_transaction == "Inward" else -1),
 				"warehouse": d.get("warehouse"),
 				"batch_no": d.get("batch_no"),
 				"serial_no": d.get("serial_no"),
@@ -1272,6 +1297,9 @@
 	else:
 		query = query.orderby(batch_table.creation)
 
+	if kwargs.get("ignore_voucher_nos"):
+		query = query.where(stock_ledger_entry.voucher_no.notin(kwargs.get("ignore_voucher_nos")))
+
 	data = query.run(as_dict=True)
 	data = list(filter(lambda x: x.qty > 0, data))
 
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
index 26226f3..3151c2c 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
@@ -8,7 +8,41 @@
 
 
 class TestSerialandBatchBundle(FrappeTestCase):
-	pass
+	def test_inward_serial_batch_bundle(self):
+		pass
+
+	def test_outward_serial_batch_bundle(self):
+		pass
+
+	def test_old_batch_valuation(self):
+		pass
+
+	def test_old_batch_batchwise_valuation(self):
+		pass
+
+	def test_old_serial_no_valuation(self):
+		pass
+
+	def test_batch_not_belong_to_serial_no(self):
+		pass
+
+	def test_serial_no_not_exists(self):
+		pass
+
+	def test_serial_no_item(self):
+		pass
+
+	def test_serial_no_not_required(self):
+		pass
+
+	def test_serial_no_required(self):
+		pass
+
+	def test_batch_no_not_required(self):
+		pass
+
+	def test_batch_no_required(self):
+		pass
 
 
 def get_batch_from_bundle(bundle):
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 2162af5..ba9482a 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -22,38 +22,10 @@
 	pass
 
 
-class SerialNoNotRequiredError(ValidationError):
-	pass
-
-
-class SerialNoRequiredError(ValidationError):
-	pass
-
-
-class SerialNoQtyError(ValidationError):
-	pass
-
-
-class SerialNoItemError(ValidationError):
-	pass
-
-
 class SerialNoWarehouseError(ValidationError):
 	pass
 
 
-class SerialNoBatchError(ValidationError):
-	pass
-
-
-class SerialNoNotExistsError(ValidationError):
-	pass
-
-
-class SerialNoDuplicateError(ValidationError):
-	pass
-
-
 class SerialNo(StockController):
 	def __init__(self, *args, **kwargs):
 		super(SerialNo, self).__init__(*args, **kwargs)
@@ -69,6 +41,15 @@
 			)
 
 		self.set_maintenance_status()
+		self.validate_warehouse()
+
+	def validate_warehouse(self):
+		if not self.get("__islocal"):
+			item_code, warehouse = frappe.db.get_value("Serial No", self.name, ["item_code", "warehouse"])
+			if not self.via_stock_ledger and item_code != self.item_code:
+				frappe.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
+			if not self.via_stock_ledger and warehouse != self.warehouse:
+				frappe.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
 
 	def set_maintenance_status(self):
 		if not self.warranty_expiry_date and not self.amc_expiry_date:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 17e6d83..2c8e7a7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -744,8 +744,11 @@
 							no_batch_serial_number_value = !d.batch_no;
 						}
 
-						if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) {
+						if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
+							frappe.flags.dialog_set = true;
 							erpnext.stock.select_batch_and_serial_no(frm, d);
+						} else {
+							frappe.flags.dialog_set = false;
 						}
 					}
 				}
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index b1868bb..4004c00 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -181,21 +181,25 @@
 				bundle_doc.flags.ignore_permissions = True
 				bundle_doc.save()
 				item.serial_and_batch_bundle = bundle_doc.name
-			elif item.serial_and_batch_bundle:
-				pass
+			elif item.serial_and_batch_bundle and not item.qty and not item.valuation_rate:
+				bundle_doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
+
+				item.qty = bundle_doc.total_qty
+				item.valuation_rate = bundle_doc.avg_rate
 
 	def remove_items_with_no_change(self):
 		"""Remove items if qty or rate is not changed"""
 		self.difference_amount = 0.0
 
 		def _changed(item):
+			if item.current_serial_and_batch_bundle:
+				self.calculate_difference_amount(item, frappe._dict({}))
+				return True
+
 			item_dict = get_stock_balance_for(
 				item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no
 			)
 
-			if item.current_serial_and_batch_bundle:
-				return True
-
 			if (item.qty is None or item.qty == item_dict.get("qty")) and (
 				item.valuation_rate is None or item.valuation_rate == item_dict.get("rate")
 			):
@@ -210,11 +214,7 @@
 
 				item.current_qty = item_dict.get("qty")
 				item.current_valuation_rate = item_dict.get("rate")
-				self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
-					item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")
-				) - flt(item_dict.get("qty"), item.precision("qty")) * flt(
-					item_dict.get("rate"), item.precision("valuation_rate")
-				)
+				self.calculate_difference_amount(item, item_dict)
 				return True
 
 		items = list(filter(lambda d: _changed(d), self.items))
@@ -231,6 +231,13 @@
 				item.idx = i + 1
 			frappe.msgprint(_("Removed items with no change in quantity or value."))
 
+	def calculate_difference_amount(self, item, item_dict):
+		self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
+			item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")
+		) - flt(item_dict.get("qty"), item.precision("qty")) * flt(
+			item_dict.get("rate"), item.precision("valuation_rate")
+		)
+
 	def validate_data(self):
 		def _get_msg(row_num, msg):
 			return _("Row # {0}:").format(row_num + 1) + " " + msg
@@ -643,7 +650,14 @@
 
 		sl_entries = []
 		for row in self.items:
-			if not (row.item_code == item_code and row.batch_no == batch_no):
+			if (
+				not (row.item_code == item_code and row.batch_no == batch_no)
+				and not row.serial_and_batch_bundle
+			):
+				continue
+
+			if row.current_serial_and_batch_bundle:
+				self.recalculate_qty_for_serial_and_batch_bundle(row)
 				continue
 
 			current_qty = get_batch_qty_for_stock_reco(
@@ -677,6 +691,27 @@
 		if sl_entries:
 			self.make_sl_entries(sl_entries)
 
+	def recalculate_qty_for_serial_and_batch_bundle(self, row):
+		doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
+		precision = doc.entries[0].precision("qty")
+
+		for d in doc.entries:
+			qty = (
+				get_batch_qty(
+					d.batch_no,
+					doc.warehouse,
+					posting_date=doc.posting_date,
+					posting_time=doc.posting_time,
+					ignore_voucher_nos=[doc.voucher_no],
+				)
+				or 0
+			) * -1
+
+			if flt(d.qty, precision) == flt(qty, precision):
+				continue
+
+			d.db_set("qty", qty)
+
 
 def get_batch_qty_for_stock_reco(
 	item_code, warehouse, batch_no, posting_date, posting_time, voucher_no
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 316b731..a04e2da 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -694,10 +694,12 @@
 			item_code=item_code, posting_time="09:00:00", target=warehouse, qty=100, basic_rate=700
 		)
 
+		batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)
+
 		# Removed 50 Qty, Balace Qty 50
 		se2 = make_stock_entry(
 			item_code=item_code,
-			batch_no=se1.items[0].batch_no,
+			batch_no=batch_no,
 			posting_time="10:00:00",
 			source=warehouse,
 			qty=50,
@@ -709,15 +711,23 @@
 			item_code=item_code,
 			posting_time="11:00:00",
 			warehouse=warehouse,
-			batch_no=se1.items[0].batch_no,
+			batch_no=batch_no,
 			qty=100,
 			rate=100,
 		)
 
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"is_cancelled": 0, "voucher_no": stock_reco.name, "actual_qty": ("<", 0)},
+			fields=["actual_qty"],
+		)
+
+		self.assertEqual(flt(sle[0].actual_qty), flt(-50.0))
+
 		# Removed 50 Qty, Balace Qty 50
 		make_stock_entry(
 			item_code=item_code,
-			batch_no=se1.items[0].batch_no,
+			batch_no=batch_no,
 			posting_time="12:00:00",
 			source=warehouse,
 			qty=50,
@@ -741,12 +751,20 @@
 		sle = frappe.get_all(
 			"Stock Ledger Entry",
 			filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0},
-			fields=["qty_after_transaction"],
+			fields=["qty_after_transaction", "actual_qty", "voucher_type", "voucher_no"],
 			order_by="posting_time desc, creation desc",
 		)
 
 		self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0))
 
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"is_cancelled": 0, "voucher_no": stock_reco.name, "actual_qty": ("<", 0)},
+			fields=["actual_qty"],
+		)
+
+		self.assertEqual(flt(sle[0].actual_qty), flt(-100.0))
+
 	def test_update_stock_reconciliation_while_reposting(self):
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
@@ -914,7 +932,7 @@
 					"do_not_submit": True,
 				}
 			)
-		)
+		).name
 
 	sr.append(
 		"items",
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index 8e148f7..8738f4a 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -17,6 +17,7 @@
   "amount",
   "allow_zero_valuation_rate",
   "serial_no_and_batch_section",
+  "add_serial_batch_bundle",
   "serial_and_batch_bundle",
   "batch_no",
   "column_break_11",
@@ -203,11 +204,16 @@
    "label": "Current Serial / Batch Bundle",
    "options": "Serial and Batch Bundle",
    "read_only": 1
+  },
+  {
+   "fieldname": "add_serial_batch_bundle",
+   "fieldtype": "Button",
+   "label": "Add Serial / Batch No"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2023-05-09 18:42:19.224916",
+ "modified": "2023-05-27 17:35:31.026852",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation Item",
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 0081ccf..77b6de1 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -78,6 +78,12 @@
 
 		self.set_serial_and_batch_bundle(sn_doc)
 
+	def validate_actual_qty(self, sn_doc):
+		precision = sn_doc.precision("total_qty")
+		if flt(sn_doc.total_qty, precision) != flt(self.sle.actual_qty, precision):
+			msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {sn_doc.name} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}"
+			frappe.throw(_(msg))
+
 	def validate_item(self):
 		msg = ""
 		if self.sle.actual_qty > 0:
@@ -214,6 +220,8 @@
 
 	def submit_serial_and_batch_bundle(self):
 		doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
+		self.validate_actual_qty(doc)
+
 		doc.flags.ignore_voucher_validation = True
 		doc.submit()
 
@@ -426,9 +434,6 @@
 			)
 		else:
 			entries = self.get_batch_no_ledgers()
-			if frappe.flags.add_breakpoint:
-				breakpoint()
-
 			self.batch_avg_rate = defaultdict(float)
 			self.available_qty = defaultdict(float)
 			self.stock_value_differece = defaultdict(float)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 4694b29..01ba491 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -676,7 +676,7 @@
 
 		if (
 			sle.voucher_type == "Stock Reconciliation"
-			and sle.batch_no
+			and (sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle))
 			and sle.voucher_detail_no
 			and sle.actual_qty < 0
 		):
@@ -734,9 +734,17 @@
 			self.update_outgoing_rate_on_transaction(sle)
 
 	def reset_actual_qty_for_stock_reco(self, sle):
-		current_qty = frappe.get_cached_value(
-			"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
-		)
+		if sle.serial_and_batch_bundle:
+			current_qty = frappe.get_cached_value(
+				"Serial and Batch Bundle", sle.serial_and_batch_bundle, "total_qty"
+			)
+
+			if current_qty is not None:
+				current_qty = abs(current_qty)
+		else:
+			current_qty = frappe.get_cached_value(
+				"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
+			)
 
 		if current_qty:
 			sle.actual_qty = current_qty * -1
@@ -1524,7 +1532,7 @@
 	next_stock_reco_detail = get_next_stock_reco(args)
 	if next_stock_reco_detail:
 		detail = next_stock_reco_detail[0]
-		if detail.batch_no:
+		if detail.batch_no or (detail.serial_and_batch_bundle and detail.has_batch_no):
 			regenerate_sle_for_batch_stock_reco(detail)
 
 		# add condition to update SLEs before this date & time
@@ -1602,7 +1610,9 @@
 			sle.voucher_no,
 			sle.item_code,
 			sle.batch_no,
+			sle.serial_and_batch_bundle,
 			sle.actual_qty,
+			sle.has_batch_no,
 		)
 		.where(
 			(sle.item_code == kwargs.get("item_code"))