Merge pull request #25849 from nextchamp-saqib/pos-rate-as-identifier

fix(pos): cannot add same item with different rates
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 4f4f1b2..ae3f9e3 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -241,10 +241,8 @@
 			events: {
 				get_frm: () => this.frm,
 
-				cart_item_clicked: (item_code, batch_no, uom) => {
-					const search_field = batch_no ? 'batch_no' : 'item_code';
-					const search_value = batch_no || item_code;
-					const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom);
+				cart_item_clicked: (item_code, batch_no, uom, rate) => {
+					const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
 					this.item_details.toggle_item_details_section(item_row);
 				},
 
@@ -275,18 +273,25 @@
 					this.cart.toggle_numpad(minimize);
 				},
 
-				form_updated: async (cdt, cdn, fieldname, value) => {
+				form_updated: (cdt, cdn, fieldname, value) => {
 					const item_row = frappe.model.get_doc(cdt, cdn);
 					if (item_row && item_row[fieldname] != value) {
 
-						const { item_code, batch_no, uom } = this.item_details.current_item;
+						const { item_code, batch_no, uom, rate } = this.item_details.current_item;
 						const event = {
 							field: fieldname,
 							value,
-							item: { item_code, batch_no, uom }
+							item: { item_code, batch_no, uom, rate }
 						}
 						return this.on_cart_update(event)
 					}
+
+					return Promise.resolve();
+				},
+
+				highlight_cart_item: (item) => {
+					const cart_item = this.cart.get_cart_item(item);
+					this.cart.toggle_item_highlight(cart_item);
 				},
 
 				item_field_focused: (fieldname) => {
@@ -501,8 +506,8 @@
 		let item_row = undefined;
 		try {
 			let { field, value, item } = args;
-			const { item_code, batch_no, serial_no, uom } = item;
-			item_row = this.get_item_from_frm(item_code, batch_no, uom);
+			const { item_code, batch_no, serial_no, uom, rate } = item;
+			item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
 
 			const item_selected_from_selector = field === 'qty' && value === "+1"
 
@@ -535,7 +540,7 @@
 
 				item_selected_from_selector && (value = flt(value))
 
-				const args = { item_code, batch_no, [field]: value };
+				const args = { item_code, batch_no, rate, [field]: value };
 
 				if (serial_no) {
 					await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
@@ -550,9 +555,11 @@
 					await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
 
 				await this.trigger_new_item_events(item_row);
-
-				this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
+				
 				this.update_cart_html(item_row);
+
+				this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row);
+				this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
 			}
 
 		} catch (error) {
@@ -563,12 +570,13 @@
 		}
 	}
 
-	get_item_from_frm(item_code, batch_no, uom) {
+	get_item_from_frm(item_code, batch_no, uom, rate) {
 		const has_batch_no = batch_no;
 		return this.frm.doc.items.find(
 			i => i.item_code === item_code
 				&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
 				&& (i.uom === uom)
+				&& (i.rate == rate)
 		);
 	}
 
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 8a98973..f5019f5 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -184,7 +184,8 @@
 			const item_code = unescape($cart_item.attr('data-item-code'));
 			const batch_no = unescape($cart_item.attr('data-batch-no'));
 			const uom = unescape($cart_item.attr('data-uom'));
-			me.events.cart_item_clicked(item_code, batch_no, uom);
+			const rate = unescape($cart_item.attr('data-rate'));
+			me.events.cart_item_clicked(item_code, batch_no, uom, rate);
 			this.numpad_value = '';
 		});
 
@@ -520,28 +521,34 @@
 		}
 	}
 
-	get_cart_item({ item_code, batch_no, uom }) {
+	get_cart_item({ item_code, batch_no, uom, rate }) {
 		const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
 		const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
 		const uom_attr = `[data-uom="${escape(uom)}"]`;
+		const rate_attr = `[data-rate="${escape(rate)}"]`;
 
 		const item_selector = batch_no ?
-			`.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
+			`.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`;
 
 		return this.$cart_items_wrapper.find(item_selector);
 	}
 
+	get_item_from_frm(item) {
+		const doc = this.events.get_frm().doc;
+		const { item_code, batch_no, uom, rate } = item;
+		const search_field = batch_no ? 'batch_no' : 'item_code';
+		const search_value = batch_no || item_code;
+
+		return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate);
+	}
+
 	update_item_html(item, remove_item) {
 		const $item = this.get_cart_item(item);
 
 		if (remove_item) {
 			$item && $item.next().remove() && $item.remove();
 		} else {
-			const { item_code, batch_no, uom } = item;
-			const search_field = batch_no ? 'batch_no' : 'item_code';
-			const search_value = batch_no || item_code;
-			const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom);
-
+			const item_row = this.get_item_from_frm(item);
 			this.render_cart_item(item_row, $item);
 		}
 
@@ -559,7 +566,7 @@
 			this.$cart_items_wrapper.append(
 				`<div class="cart-item-wrapper"
 						data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
-						data-batch-no="${escape(item_data.batch_no || '')}">
+						data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
 				</div>
 				<div class="seperator"></div>`
 			)
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index 32a4556..df62696 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -54,13 +54,24 @@
 		this.$dicount_section = this.$component.find('.discount-section');
 	}
 
-	toggle_item_details_section(item) {
-		const { item_code, batch_no, uom } = this.current_item;
+	has_item_has_changed(item) {
+		const { item_code, batch_no, uom, rate } = this.current_item;
 		const item_code_is_same = item && item_code === item.item_code;
 		const batch_is_same = item && batch_no == item.batch_no;
 		const uom_is_same = item && uom === item.uom;
+		const rate_is_same = item && rate === item.rate;
+		
+		if (!item)
+			return false;
 
-		this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
+		if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same)
+			return false;
+
+		return true;
+	}
+
+	toggle_item_details_section(item) {
+		this.item_has_changed = this.has_item_has_changed(item);
 
 		this.events.toggle_item_selector(this.item_has_changed);
 		this.toggle_component(this.item_has_changed);
@@ -72,11 +83,12 @@
 			this.item_row = item;
 			this.currency = this.events.get_frm().doc.currency;
 
-			this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
+			this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate };
 
 			this.render_dom(item);
 			this.render_discount_dom(item);
 			this.render_form(item);
+			this.events.highlight_cart_item(item);
 		} else {
 			this.validate_serial_batch_item();
 			this.current_item = {};
@@ -198,12 +210,14 @@
 			if (this.allow_rate_change) {
 				this.rate_control.df.onchange = function() {
 					if (this.value || flt(this.value) === 0) {
+						me.events.set_value_in_current_cart_item('rate', this.value);
 						me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
 							const item_row = frappe.get_doc(me.doctype, me.name);
 							const doc = me.events.get_frm().doc;
 							me.$item_price.html(format_currency(item_row.rate, doc.currency));
 							me.render_discount_dom(item_row);
 						});
+						me.current_item.rate = this.value;
 					}
 				};
 			} else {
@@ -292,11 +306,7 @@
 
 		frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
 			const field_control = this[`${fieldname}_control`];
-			const { item_code, batch_no, uom } = this.current_item;
-			const item_code_is_same = item_code === item_row.item_code;
-			const batch_is_same = batch_no == item_row.batch_no;
-			const uom_is_same = uom === item_row.uom;
-			const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false;
+			const item_is_same = !this.has_item_has_changed(item_row);
 
 			if (item_is_same && field_control && field_control.get_value() !== value) {
 				field_control.set_value(value);
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index b6109ac..5b48725 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -78,7 +78,7 @@
 	get_item_html(item) {
 		const me = this;
 		// eslint-disable-next-line no-unused-vars
-		const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
+		const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
 		const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
 
 		let qty_to_display = actual_qty;
@@ -112,6 +112,7 @@
 			`<div class="item-wrapper"
 				data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
 				data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+				data-rate="${escape(price_list_rate)}"
 				title="${item.item_name}">
 
 				${get_item_image_html()}
@@ -120,7 +121,7 @@
 					<div class="item-name">
 						${frappe.ellipsis(item.item_name, 18)}
 					</div>
-					<div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+					<div class="item-rate">${format_currency(price_list_rate, item.currency, 0) || 0}</div>
 				</div>
 			</div>`
 		);
@@ -222,13 +223,15 @@
 			let batch_no = unescape($item.attr('data-batch-no'));
 			let serial_no = unescape($item.attr('data-serial-no'));
 			let uom = unescape($item.attr('data-uom'));
+			let rate = unescape($item.attr('data-rate'));
 
 			// escape(undefined) returns "undefined" then unescape returns "undefined"
 			batch_no = batch_no === "undefined" ? undefined : batch_no;
 			serial_no = serial_no === "undefined" ? undefined : serial_no;
 			uom = uom === "undefined" ? undefined : uom;
+			rate = rate === "undefined" ? undefined : rate;
 
-			me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
+			me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }});
 			me.set_search_value('');
 		});