fix: serial and batch selector
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 49ce6b9..3c7c787 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1527,7 +1527,6 @@
 		ste_doc.load_from_db()
 
 		# Create a stock entry to manufacture the item
-		print("remove 2 qty from each item")
 		ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 5))
 		for row in ste_doc.items:
 			if row.s_warehouse and not row.t_warehouse:
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 382ae2c..6d3af42 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -12,12 +12,12 @@
 	}
 
 	make() {
-		let label = this.item?.has_serial_no ? __('Serial No') : __('Batch No');
+		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?.batch_no) {
-			label = __('Serial No / Batch No');
+			label = __('Serial Nos / Batch Nos');
 		}
 
 		primary_label += ' ' + label;
@@ -26,7 +26,9 @@
 			title: this.item?.title || primary_label,
 			fields: this.get_dialog_fields(),
 			primary_action_label: primary_label,
-			primary_action: () => this.update_ledgers()
+			primary_action: () => this.update_ledgers(),
+			secondary_action_label: __('Edit Full Form'),
+			secondary_action: () => this.edit_full_form(),
 		});
 
 		this.dialog.set_value("qty", this.item.qty);
@@ -48,7 +50,7 @@
 
 		if (this.item.has_serial_no) {
 			fields.push({
-				fieldtype: 'Link',
+				fieldtype: 'Data',
 				fieldname: 'scan_serial_no',
 				label: __('Scan Serial No'),
 				options: 'Serial No',
@@ -279,6 +281,37 @@
 		})
 	}
 
+	edit_full_form() {
+		let bundle_id = this.item.serial_and_batch_bundle
+		if (!bundle_id) {
+			_new = frappe.model.get_new_doc(
+				"Serial and Batch Bundle", null, null, true
+			);
+
+			_new.item_code = this.item.item_code;
+			_new.warehouse = this.get_warehouse();
+			_new.has_serial_no = this.item.has_serial_no;
+			_new.has_batch_no = this.item.has_batch_no;
+			_new.type_of_transaction = this.get_type_of_transaction();
+			_new.company = this.frm.doc.company;
+			_new.voucher_type = this.frm.doc.doctype;
+			bundle_id = _new.name;
+		}
+
+		frappe.set_route("Form", "Serial and Batch Bundle", bundle_id);
+		this.dialog.hide();
+	}
+
+	get_warehouse() {
+		return (this.item?.outward ?
+			(this.item.warehouse || this.item.s_warehouse)
+			: (this.item.warehouse || this.item.t_warehouse));
+	}
+
+	get_type_of_transaction() {
+		return (this.item?.outward ? 'Outward' : 'Inward');
+	}
+
 	render_data() {
 		if (!this.frm.is_new() && this.bundle) {
 			frappe.call({
diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py
index 9e15015..76202ed 100644
--- a/erpnext/stock/deprecated_serial_batch.py
+++ b/erpnext/stock/deprecated_serial_batch.py
@@ -4,6 +4,7 @@
 from frappe.query_builder.functions import CombineDatetime, Sum
 from frappe.utils import flt
 from frappe.utils.deprecations import deprecated
+from pypika import Order
 
 
 class DeprecatedSerialNoValuation:
@@ -39,25 +40,25 @@
 		# Get rate for serial nos which has been transferred to other company
 		invalid_serial_nos = [d.name for d in all_serial_nos if d.company != self.sle.company]
 		for serial_no in invalid_serial_nos:
-			incoming_rate = frappe.db.sql(
-				"""
-				select incoming_rate
-				from `tabStock Ledger Entry`
-				where
-					company = %s
-					and serial_and_batch_bundle IS NULL
-					and actual_qty > 0
-					and is_cancelled = 0
-					and (serial_no = %s
-						or serial_no like %s
-						or serial_no like %s
-						or serial_no like %s
+			table = frappe.qb.DocType("Stock Ledger Entry")
+			incoming_rate = (
+				frappe.qb.from_(table)
+				.select(table.incoming_rate)
+				.where(
+					(
+						(table.serial_no == serial_no)
+						| (table.serial_no.like(serial_no + "\n%"))
+						| (table.serial_no.like("%\n" + serial_no))
+						| (table.serial_no.like("%\n" + serial_no + "\n%"))
 					)
-				order by posting_date desc
-				limit 1
-			""",
-				(self.sle.company, serial_no, serial_no + "\n%", "%\n" + serial_no, "%\n" + serial_no + "\n%"),
-			)
+					& (table.company == self.sle.company)
+					& (table.serial_and_batch_bundle.isnull())
+					& (table.actual_qty > 0)
+					& (table.is_cancelled == 0)
+				)
+				.orderby(table.posting_date, order=Order.desc)
+				.limit(1)
+			).run()
 
 			self.serial_no_incoming_rate[serial_no] += flt(incoming_rate[0][0]) if incoming_rate else 0
 			incoming_values += self.serial_no_incoming_rate[serial_no]
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index 858b333..b02ad71 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -8,6 +8,17 @@
 
 	refresh(frm) {
 		frm.trigger('toggle_fields');
+		frm.trigger('prepare_serial_batch_prompt');
+	},
+
+	item_code(frm) {
+		frm.clear_custom_buttons();
+		frm.trigger('prepare_serial_batch_prompt');
+	},
+
+	type_of_transaction(frm) {
+		frm.clear_custom_buttons();
+		frm.trigger('prepare_serial_batch_prompt');
 	},
 
 	warehouse(frm) {
@@ -30,6 +41,91 @@
 		frm.trigger('toggle_fields');
 	},
 
+	prepare_serial_batch_prompt(frm) {
+		if (frm.doc.docstatus === 0 && frm.doc.item_code
+			&& frm.doc.type_of_transaction === "Inward") {
+			let label = frm.doc?.has_serial_no === 1
+				? __('Serial Nos') : __('Batch Nos');
+
+			if (frm.doc?.has_serial_no === 1 && frm.doc?.has_batch_no === 1) {
+				label = __('Serial and Batch Nos');
+			}
+
+			let fields = frm.events.get_prompt_fields(frm);
+
+			frm.add_custom_button(__("Make " + label), () => {
+				frappe.prompt(fields, (data) => {
+					frm.events.add_serial_batch(frm, data);
+				}, "Add " + label, "Make " + label);
+			});
+		}
+	},
+
+	get_prompt_fields(frm) {
+		let attach_field = {
+			"label": __("Attach CSV File"),
+			"fieldname": "csv_file",
+			"fieldtype": "Attach"
+		}
+
+		if (!frm.doc.has_batch_no) {
+			attach_field.depends_on = "eval:doc.using_csv_file === 1"
+		}
+
+		let fields = [
+			{
+				"label": __("Using CSV File"),
+				"fieldname": "using_csv_file",
+				"default": 1,
+				"fieldtype": "Check",
+			},
+			attach_field,
+			{
+				"fieldtype": "Section Break",
+			}
+		]
+
+		if (frm.doc.has_serial_no) {
+			fields.push({
+				"label": "Serial Nos",
+				"fieldname": "serial_nos",
+				"fieldtype": "Small Text",
+				"depends_on": "eval:doc.using_csv_file === 0"
+			})
+		}
+
+		if (frm.doc.has_batch_no) {
+			fields = attach_field
+		}
+
+		return fields;
+	},
+
+	add_serial_batch(frm, prompt_data) {
+		frm.events.validate_prompt_data(frm, prompt_data);
+
+		frm.call({
+			method: "add_serial_batch",
+			doc: frm.doc,
+			args: {
+				"data": prompt_data,
+			},
+			callback(r) {
+				refresh_field("entries");
+			}
+		});
+	},
+
+	validate_prompt_data(frm, prompt_data) {
+		if (prompt_data.using_csv_file && !prompt_data.csv_file) {
+			frappe.throw(__("Please attach CSV file"));
+		}
+
+		if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) {
+			frappe.throw(__("Please enter serial nos"));
+		}
+	},
+
 	toggle_fields(frm) {
 		frm.fields_dict.entries.grid.update_docfield_property(
 			'serial_no', 'read_only', !frm.doc.has_serial_no
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
index 77ba13a..6955c76 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
@@ -9,13 +9,13 @@
   "item_details_tab",
   "naming_series",
   "company",
-  "warehouse",
-  "type_of_transaction",
-  "column_break_4",
-  "item_code",
   "item_name",
   "has_serial_no",
   "has_batch_no",
+  "column_break_4",
+  "item_code",
+  "warehouse",
+  "type_of_transaction",
   "serial_no_and_batch_no_tab",
   "entries",
   "quantity_and_rate_section",
@@ -84,7 +84,8 @@
    "fetch_from": "item_code.item_name",
    "fieldname": "item_name",
    "fieldtype": "Data",
-   "label": "Item Name"
+   "label": "Item Name",
+   "read_only": 1
   },
   {
    "default": "0",
@@ -243,7 +244,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-04-06 02:35:38.404537",
+ "modified": "2023-04-10 20:02:42.964309",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Bundle",
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 0b7eda9..f787caa 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
@@ -2,6 +2,7 @@
 # For license information, please see license.txt
 
 import collections
+import csv
 from collections import defaultdict
 from typing import Dict, List
 
@@ -9,7 +10,17 @@
 from frappe import _, _dict, bold
 from frappe.model.document import Document
 from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import add_days, cint, flt, get_link_to_form, nowtime, today
+from frappe.utils import (
+	add_days,
+	cint,
+	cstr,
+	flt,
+	get_link_to_form,
+	now,
+	nowtime,
+	parse_json,
+	today,
+)
 
 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
@@ -626,6 +637,173 @@
 		self.delink_reference_from_batch()
 		self.clear_table()
 
+	@frappe.whitelist()
+	def add_serial_batch(self, data):
+		serial_nos, batch_nos = [], []
+		if isinstance(data, str):
+			data = parse_json(data)
+
+		if data.get("csv_file"):
+			serial_nos, batch_nos = get_serial_batch_from_csv(self.item_code, data.get("csv_file"))
+		else:
+			serial_nos, batch_nos = get_serial_batch_from_data(self.item_code, data)
+
+		if not serial_nos and not batch_nos:
+			return
+
+		if serial_nos:
+			self.set("entries", serial_nos)
+		elif batch_nos:
+			self.set("entries", batch_nos)
+
+
+def get_serial_batch_from_csv(item_code, file_path):
+	file_path = frappe.get_site_path() + file_path
+	serial_nos = []
+	batch_nos = []
+
+	with open(file_path, "r") as f:
+		reader = csv.reader(f)
+		serial_nos, batch_nos = parse_csv_file_to_get_serial_batch(reader)
+
+	if serial_nos:
+		make_serial_nos(item_code, serial_nos)
+
+	print(batch_nos)
+	if batch_nos:
+		make_batch_nos(item_code, batch_nos)
+
+	return serial_nos, batch_nos
+
+
+def parse_csv_file_to_get_serial_batch(reader):
+	has_serial_no, has_batch_no = False, False
+	serial_nos = []
+	batch_nos = []
+
+	for index, row in enumerate(reader):
+		if index == 0:
+			has_serial_no = row[0] == "Serial No"
+			has_batch_no = row[0] == "Batch No"
+			continue
+
+		if not row[0]:
+			continue
+
+		if has_serial_no or (has_serial_no and has_batch_no):
+			_dict = {"serial_no": row[0], "qty": 1}
+
+			if has_batch_no:
+				_dict.update(
+					{
+						"batch_no": row[1],
+						"qty": row[2],
+					}
+				)
+
+			serial_nos.append(_dict)
+		elif has_batch_no:
+			batch_nos.append(
+				{
+					"batch_no": row[0],
+					"qty": row[1],
+				}
+			)
+
+	return serial_nos, batch_nos
+
+
+def get_serial_batch_from_data(item_code, kwargs):
+	serial_nos = []
+	batch_nos = []
+	if kwargs.get("serial_nos"):
+		data = parse_serial_nos(kwargs.get("serial_nos"))
+		for serial_no in data:
+			if not serial_no:
+				continue
+			serial_nos.append({"serial_no": serial_no, "qty": 1})
+
+		make_serial_nos(item_code, serial_nos)
+
+	return serial_nos, batch_nos
+
+
+def make_serial_nos(item_code, serial_nos):
+	item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
+
+	serial_nos = [d.get("serial_no") for d in serial_nos if d.get("serial_no")]
+
+	serial_nos_details = []
+	user = frappe.session.user
+	for serial_no in serial_nos:
+		serial_nos_details.append(
+			(
+				serial_no,
+				serial_no,
+				now(),
+				now(),
+				user,
+				user,
+				item.item_code,
+				item.item_name,
+				item.description,
+				"Inactive",
+			)
+		)
+
+	fields = [
+		"name",
+		"serial_no",
+		"creation",
+		"modified",
+		"owner",
+		"modified_by",
+		"item_code",
+		"item_name",
+		"description",
+		"status",
+	]
+
+	frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
+
+	frappe.msgprint(_("Serial Nos are created successfully"))
+
+
+def make_batch_nos(item_code, batch_nos):
+	item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
+
+	batch_nos = [d.get("batch_no") for d in batch_nos if d.get("batch_no")]
+
+	batch_nos_details = []
+	user = frappe.session.user
+	for batch_no in batch_nos:
+		batch_nos_details.append(
+			(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
+		)
+
+	fields = [
+		"name",
+		"batch_id",
+		"creation",
+		"modified",
+		"owner",
+		"modified_by",
+		"item",
+		"item_name",
+		"description",
+	]
+
+	frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
+
+	frappe.msgprint(_("Batch Nos are created successfully"))
+
+
+def parse_serial_nos(data):
+	if isinstance(data, list):
+		return data
+
+	return [s.strip() for s in cstr(data).strip().upper().replace(",", "\n").split("\n") if s.strip()]
+
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
@@ -690,13 +868,13 @@
 @frappe.whitelist()
 def add_serial_batch_ledgers(entries, child_row, doc) -> object:
 	if isinstance(child_row, str):
-		child_row = frappe._dict(frappe.parse_json(child_row))
+		child_row = frappe._dict(parse_json(child_row))
 
 	if isinstance(entries, str):
-		entries = frappe.parse_json(entries)
+		entries = parse_json(entries)
 
 	if doc and isinstance(doc, str):
-		parent_doc = frappe.parse_json(doc)
+		parent_doc = parse_json(doc)
 
 	if frappe.db.exists("Serial and Batch Bundle", child_row.serial_and_batch_bundle):
 		doc = update_serial_batch_no_ledgers(entries, child_row, parent_doc)