fix: scan for serial or batch in bundle (#38534)

diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 9267801..0de6774 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -31,8 +31,19 @@
 			secondary_action: () => this.edit_full_form(),
 		});
 
-		this.dialog.set_value("qty", this.item.qty);
+		this.dialog.set_value("qty", this.item.qty).then(() => {
+			if (this.item.serial_no) {
+				this.dialog.set_value("scan_serial_no", this.item.serial_no);
+				frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
+			} else if (this.item.batch_no) {
+				this.dialog.set_value("scan_batch_no", this.item.batch_no);
+				frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
+			}
+		});
+
 		this.dialog.show();
+		this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
+		this.$scan_btn.css("display", "inline");
 	}
 
 	get_serial_no_filters() {
@@ -95,6 +106,7 @@
 		if (this.item.has_serial_no) {
 			fields.push({
 				fieldtype: 'Data',
+				options: 'Barcode',
 				fieldname: 'scan_serial_no',
 				label: __('Scan Serial No'),
 				get_query: () => {
@@ -106,15 +118,10 @@
 			});
 		}
 
-		if (this.item.has_batch_no && this.item.has_serial_no) {
-			fields.push({
-				fieldtype: 'Column Break',
-			});
-		}
-
-		if (this.item.has_batch_no) {
+		if (this.item.has_batch_no && !this.item.has_serial_no) {
 			fields.push({
 				fieldtype: 'Data',
+				options: 'Barcode',
 				fieldname: 'scan_batch_no',
 				label: __('Scan Batch No'),
 				onchange: () => this.update_serial_batch_no()
@@ -309,6 +316,14 @@
 	}
 
 	get_auto_data() {
+		if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) {
+			return;
+		}
+
+		if (this.item.serial_no || this.item.batch_no) {
+			return;
+		}
+
 		let { qty, based_on } = this.dialog.get_values();
 
 		if (!based_on) {
@@ -340,16 +355,57 @@
 		const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
 
 		if (scan_serial_no) {
-			this.dialog.fields_dict.entries.df.data.push({
-				serial_no: scan_serial_no
+			let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+				if (d.serial_no === scan_serial_no) {
+					return d
+				}
 			});
 
-			this.dialog.fields_dict.scan_serial_no.set_value('');
+			if (existing_row?.length) {
+				frappe.throw(__('Serial No {0} already exists', [scan_serial_no]));
+			}
+
+			if (!this.item.has_batch_no) {
+				this.dialog.fields_dict.entries.df.data.push({
+					serial_no: scan_serial_no
+				});
+
+				this.dialog.fields_dict.scan_serial_no.set_value('');
+			} else {
+				frappe.call({
+					method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no',
+					args: {
+						serial_no: scan_serial_no,
+					},
+					callback: (r) => {
+						if (r.message) {
+							this.dialog.fields_dict.entries.df.data.push({
+								serial_no: scan_serial_no,
+								batch_no: r.message
+							});
+
+							this.dialog.fields_dict.scan_serial_no.set_value('');
+						}
+					}
+
+				})
+			}
 		} else if (scan_batch_no) {
-			this.dialog.fields_dict.entries.df.data.push({
-				batch_no: scan_batch_no
+			let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+				if (d.batch_no === scan_batch_no) {
+					return d
+				}
 			});
 
+			if (existing_row?.length) {
+				existing_row[0].qty += 1;
+			} else {
+				this.dialog.fields_dict.entries.df.data.push({
+					batch_no: scan_batch_no,
+					qty: 1
+				});
+			}
+
 			this.dialog.fields_dict.scan_batch_no.set_value('');
 		}
 
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 88929ea..108a2e0 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
@@ -1745,3 +1745,8 @@
 			batches[key].qty += d.qty
 
 	return batches
+
+
+@frappe.whitelist()
+def get_batch_no_from_serial_no(serial_no):
+	return frappe.get_cached_value("Serial No", serial_no, "batch_no")