[Fix] Multi-batch selection issue in POS (#13407)

diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 0fb8730..78def26 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -85,8 +85,6 @@
 			fields: fields
 		});
 
-		this.bind_qty();
-
 		this.dialog.set_primary_action(__('Insert'), function() {
 			me.values = me.dialog.get_values();
 			if(me.validate()) {
@@ -102,17 +100,24 @@
 			}
 
 			if (d.batch_no) {
-				this.dialog.fields_dict.batches.df.data.push({
-					'batch_no': d.batch_no,
-					'actual_qty': d.actual_qty,
-					'selected_qty': d.qty,
-					'available_qty': d.actual_batch_qty
+				this.frm.doc.items.forEach(data => {
+					if(data.item_code == d.item_code) {
+						this.dialog.fields_dict.batches.df.data.push({
+							'batch_no': data.batch_no,
+							'actual_qty': data.actual_qty,
+							'selected_qty': data.qty,
+							'available_qty': data.actual_batch_qty
+						});
+					}
 				});
-
 				this.dialog.fields_dict.batches.grid.refresh();
 			}
 		}
 
+		if (this.has_batch) {
+			this.update_total_qty();
+		}
+
 		this.dialog.show();
 	},
 
@@ -161,18 +166,44 @@
 		var me = this;
 		if(this.has_batch) {
 			this.values.batches.map((batch, i) => {
-				let item_code_field = {};
-				let row = (i !== 0) ? this.frm.add_child("items", this.item) : this.item;
+				let batch_no = batch.batch_no;
+				let row = '';
+
+				if (i !== 0 && !this.batch_exists(batch_no)) {
+					row = this.frm.add_child("items", {
+						'item_code': this.item.item_code,
+						'item_name': this.item.item_name,
+						'price_list_rate': this.item.price_list_rate,
+						'rate': this.item.rate,
+						'qty': batch.selected_qty,
+						'batch_no': batch_no,
+						'actual_qty': this.item.actual_qty,
+						'discount_percentage': this.item.discount_percentage
+					});
+				} else {
+					row = this.frm.doc.items.find(i => i.batch_no === batch_no);
+				}
+
+				if (!row) {
+					row = this.item;
+				}
+
 				this.map_row_values(row, batch, 'batch_no',
 					'selected_qty', this.values.warehouse);
 			});
 		} else {
 			this.map_row_values(this.item, this.values, 'serial_no', 'qty');
 		}
+
 		refresh_field("items");
 		this.callback && this.callback(this.item);
 	},
 
+	batch_exists: function(batch) {
+		const batches = this.frm.doc.items.map(data => data.batch_no);
+		return (batches && in_list(batches, batch)) ? true : false;
+	},
+
 	map_row_values: function(row, values, number, qty_field, warehouse) {
 		row.qty = values[qty_field];
 		row[number] = values[number];
@@ -185,20 +216,15 @@
 		}
 	},
 
-	bind_qty: function() {
-		let batches_field = this.dialog.fields_dict.batches;
+	update_total_qty: function() {
 		let qty_field = this.dialog.fields_dict.qty;
-		if(batches_field) {
-			batches_field.grid.wrapper.on('change', function() {
-				let total_qty = 0;
-				batches_field.grid.wrapper.find(
-					'input[data-fieldname="selected_qty"]').each(function() {
+		let total_qty = 0;
 
-					total_qty += Number($(this).val());
-				});
-				qty_field.set_input(total_qty);
-			});
-		}
+		this.dialog.fields_dict.batches.df.data.forEach(data => {
+			total_qty += flt(data.selected_qty);
+		});
+
+		qty_field.set_input(total_qty);
 	},
 
 	get_batch_fields: function() {
@@ -230,7 +256,10 @@
 								if(row === this.grid_row) {
 									return "";
 								}
-								return row.on_grid_fields_dict.batch_no.get_value();
+
+								if (row.on_grid_fields_dict.batch_no) {
+									return row.on_grid_fields_dict.batch_no.get_value();
+								}
 							});
 							if(selected_batches.includes(val)) {
 								this.set_value("");
@@ -293,6 +322,8 @@
 							} else {
 								this.grid.refresh();
 							}
+
+							me.update_total_qty();
 						}
 					},
 				],
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index a027a98..400cd95 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -23,7 +23,7 @@
 
 frappe.pages['point-of-sale'].refresh = function(wrapper) {
 	if (wrapper.pos) {
-		cur_frm = wrapper.pos.frm;
+		wrapper.pos.make_new_invoice();
 	}
 
 	if (frappe.flags.is_offline) {
@@ -96,8 +96,8 @@
 			wrapper: this.wrapper.find('.cart-container'),
 			events: {
 				on_customer_change: (customer) => this.frm.set_value('customer', customer),
-				on_field_change: (item_code, field, value) => {
-					this.update_item_in_cart(item_code, field, value);
+				on_field_change: (item_code, field, value, batch_no) => {
+					this.update_item_in_cart(item_code, field, value, batch_no);
 				},
 				on_numpad: (value) => {
 					if (value == 'Pay') {
@@ -158,10 +158,12 @@
 		});
 	}
 
-	update_item_in_cart(item_code, field='qty', value=1) {
+	update_item_in_cart(item_code, field='qty', value=1, batch_no) {
 		frappe.dom.freeze();
-		if(this.cart.exists(item_code)) {
-			const item = this.frm.doc.items.find(i => i.item_code === item_code);
+		if(this.cart.exists(item_code, batch_no)) {
+			const search_field = batch_no ? 'batch_no' : 'item_code';
+			const search_value = batch_no || item_code;
+			const item = this.frm.doc.items.find(i => i[search_field] === search_value);
 			frappe.flags.hide_serial_batch_dialog = false;
 
 			if (typeof value === 'string' && !in_list(['serial_no', 'batch_no'], field)) {
@@ -219,29 +221,31 @@
 		]);
 	}
 
-	select_batch_and_serial_no(item) {
+	select_batch_and_serial_no(row) {
 		frappe.dom.unfreeze();
 
-		erpnext.show_serial_batch_selector(this.frm, item, () => {
-			this.update_item_in_frm(item, 'qty', item.qty)
-				.then(() => {
-					// update cart
-					frappe.run_serially([
-						() => {
-							if (item.qty === 0) {
-								frappe.model.clear_doc(item.doctype, item.name);
-							}
-						},
-						() => this.update_cart_data(item)
-					]);
-				});
+		erpnext.show_serial_batch_selector(this.frm, row, () => {
+			this.frm.doc.items.forEach(item => {
+				this.update_item_in_frm(item, 'qty', item.qty)
+					.then(() => {
+						// update cart
+						frappe.run_serially([
+							() => {
+								if (item.qty === 0) {
+									frappe.model.clear_doc(item.doctype, item.name);
+								}
+							},
+							() => this.update_cart_data(item)
+						]);
+					});
+			})
 		}, () => {
-			this.on_close(item);
+			this.on_close(row);
 		}, true);
 	}
 
 	on_close(item) {
-		if (!this.cart.exists(item.item_code) && item.qty) {
+		if (!this.cart.exists(item.item_code, item.batch_no) && item.qty) {
 			frappe.model.clear_doc(item.doctype, item.name);
 		}
 	}
@@ -492,6 +496,11 @@
 		//
 		// }).addClass('visible-xs');
 
+		this.page.add_menu_item(__("Form View"), function () {
+			frappe.model.sync(me.frm.doc);
+			frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name);
+		});
+
 		this.page.add_menu_item(__("POS Profile"), function () {
 			frappe.set_route('List', 'POS Profile');
 		});
@@ -602,11 +611,15 @@
 		this.customer_field.set_value("");
 		this.frm.msgbox = "";
 
+		let total_item_qty = 0.0;
+		this.frm.set_value("pos_total_qty",total_item_qty);
+
 		this.$discount_amount.find('input:text').val('');
 		this.wrapper.find('.grand-total-value').text(
 			format_currency(this.frm.doc.grand_total, this.frm.currency));
 		this.wrapper.find('.rounded-total-value').text(
 			format_currency(this.frm.doc.rounded_total, this.frm.currency));
+		this.$qty_total.find(".quantity-total").text(total_item_qty);
 
 		const customer = this.frm.doc.customer;
 		this.customer_field.set_value(customer);
@@ -721,7 +734,7 @@
 					total_item_qty += d.qty;
 				}
 		});
-		this.$qty_total.find('.quantity-total').text(total_item_qty)
+		this.$qty_total.find('.quantity-total').text(total_item_qty);
 		this.frm.set_value("pos_total_qty",total_item_qty);
 	}
 
@@ -804,10 +817,11 @@
 						this.numpad.reset_value();
 					} else {
 						const item_code = this.selected_item.attr('data-item-code');
+						const batch_no = this.selected_item.attr('data-batch-no');
 						const field = this.selected_item.active_field;
 						const value = this.numpad.get_value();
 
-						this.events.on_field_change(item_code, field, value);
+						this.events.on_field_change(item_code, field, value, batch_no);
 					}
 				}
 
@@ -835,7 +849,7 @@
 	add_item(item) {
 		this.$empty_state.hide();
 
-		if (this.exists(item.item_code)) {
+		if (this.exists(item.item_code, item.batch_no)) {
 			// update quantity
 			this.update_item(item);
 		} else if (flt(item.qty) > 0.0) {
@@ -848,7 +862,10 @@
 	}
 
 	update_item(item) {
-		const $item = this.$cart_items.find(`[data-item-code="${item.item_code}"]`);
+		const item_selector = item.batch_no ?
+			`[data-batch-no="${item.batch_no}"]` : `[data-item-code="${item.item_code}"]`;
+
+		const $item = this.$cart_items.find(item_selector);
 
 		if(item.qty > 0) {
 			const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
@@ -870,7 +887,8 @@
 		const rate = format_currency(item.rate, this.frm.doc.currency);
 		const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
 		return `
-			<div class="list-item indicator ${indicator_class}" data-item-code="${item.item_code}" title="Item: ${item.item_name}  Available Qty: ${item.actual_qty}">
+			<div class="list-item indicator ${indicator_class}" data-item-code="${item.item_code}"
+				data-batch-no="${item.batch_no}" title="Item: ${item.item_name}  Available Qty: ${item.actual_qty}">
 				<div class="item-name list-item__content list-item__content--flex-1.5 ellipsis">
 					${item.item_name}
 				</div>
@@ -911,8 +929,11 @@
 		return this.item_data[item_code];
 	}
 
-	exists(item_code) {
-		let $item = this.$cart_items.find(`[data-item-code="${item_code}"]`);
+	exists(item_code, batch_no) {
+		const is_exists = batch_no ?
+			`[data-batch-no="${batch_no}"]` : `[data-item-code="${item_code}"]`;
+
+		let $item = this.$cart_items.find(is_exists);
 		return $item.length > 0;
 	}