Merge pull request #35699 from rohitwaghchaure/fixed-added-validation-for-incorrect-type

fix: added validation for incorrect type
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 953a893..9335567 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -2340,14 +2340,11 @@
 
 	frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
 		if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) {
-			item_row.outward = frm.doc.is_return ? 0 : 1;
+			item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward";
 		} else {
-			item_row.outward = frm.doc.is_return ? 1 : 0;
+			item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward";
 		}
 
-		item_row.type_of_transaction = (item_row.outward === 1
-			? "Outward":"Inward");
-
 		new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => {
 			if (r) {
 				let update_values = {
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 58aa8d7..a859a67 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -350,6 +350,38 @@
 		}
 
 	},
+
+	pick_serial_and_batch_bundle(frm, cdt, cdn, type_of_transaction, warehouse_field) {
+		let item_row = frappe.get_doc(cdt, cdn);
+		item_row.type_of_transaction = type_of_transaction;
+
+		frappe.db.get_value("Item", item_row.item_code, ["has_batch_no", "has_serial_no"])
+			.then((r) => {
+				item_row.has_batch_no = r.message.has_batch_no;
+				item_row.has_serial_no = r.message.has_serial_no;
+
+				frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
+					new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => {
+						if (r) {
+							let update_values = {
+								"serial_and_batch_bundle": r.name,
+								"qty": Math.abs(r.total_qty)
+							}
+
+							if (!warehouse_field) {
+								warehouse_field = "warehouse";
+							}
+
+							if (r.warehouse) {
+								update_values[warehouse_field] = r.warehouse;
+							}
+
+							frappe.model.set_value(item_row.doctype, item_row.name, update_values);
+						}
+					});
+				});
+		});
+	}
 });
 
 erpnext.utils.select_alternate_items = function(opts) {
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index f9eec2a..27a7968 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -26,7 +26,7 @@
 			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_bundle_entries(),
 			secondary_action_label: __('Edit Full Form'),
 			secondary_action: () => this.edit_full_form(),
 		});
@@ -36,7 +36,7 @@
 	}
 
 	get_serial_no_filters() {
-		let warehouse = this.item?.outward ?
+		let warehouse = this.item?.type_of_transaction === "Outward" ?
 			(this.item.warehouse || this.item.s_warehouse) : "";
 
 		return {
@@ -121,7 +121,7 @@
 			});
 		}
 
-		if (this.item?.outward) {
+		if (this.item?.type_of_transaction === "Outward") {
 			fields = [...this.get_filter_fields(), ...fields];
 		} else {
 			fields = [...fields, ...this.get_attach_field()];
@@ -267,7 +267,7 @@
 					label: __('Batch No'),
 					in_list_view: 1,
 					get_query: () => {
-						if (!this.item.outward) {
+						if (this.item.type_of_transaction !== "Outward") {
 							return {
 								filters: {
 									'item': this.item.item_code,
@@ -356,7 +356,7 @@
 		this.dialog.fields_dict.entries.grid.refresh();
 	}
 
-	update_ledgers() {
+	update_bundle_entries() {
 		let entries = this.dialog.get_values().entries;
 		let warehouse = this.dialog.get_value('warehouse');
 
@@ -390,7 +390,7 @@
 			_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.type_of_transaction = this.item.type_of_transaction;
 			_new.company = this.frm.doc.company;
 			_new.voucher_type = this.frm.doc.doctype;
 			bundle_id = _new.name;
@@ -401,15 +401,11 @@
 	}
 
 	get_warehouse() {
-		return (this.item?.outward ?
+		return (this.item?.type_of_transaction === "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/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index e6b2b3b..b6e567c 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -379,7 +379,6 @@
 			frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", () => {
 				let frm = this.events.get_frm();
 				let item_row = this.item_row;
-				item_row.outward = 1;
 				item_row.type_of_transaction = "Outward";
 
 				new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => {
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 98ad8a7..87c0fae 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -317,7 +317,6 @@
 					item.has_serial_no = r.message.has_serial_no;
 					item.has_batch_no = r.message.has_batch_no;
 					item.type_of_transaction = item.qty > 0 ? "Outward":"Inward";
-					item.outward = item.qty > 0 ? 1 : 0;
 
 					item.title = item.has_serial_no ?
 						__("Select Serial No") : __("Select Batch No");
diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py
index 0237731..2f1270e 100644
--- a/erpnext/stock/deprecated_serial_batch.py
+++ b/erpnext/stock/deprecated_serial_batch.py
@@ -125,6 +125,9 @@
 			if batch_no not in self.non_batchwise_valuation_batches:
 				continue
 
+			if not self.non_batchwise_balance_qty:
+				continue
+
 			self.batch_avg_rate[batch_no] = (
 				self.non_batchwise_balance_value / self.non_batchwise_balance_qty
 			)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index f7fb633..3d497ac 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -1114,7 +1114,7 @@
 			if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
 				item.has_serial_no = r.message.has_serial_no;
 				item.has_batch_no = r.message.has_batch_no;
-				item.outward = item.s_warehouse ? 1 : 0;
+				item.type_of_transaction = item.s_warehouse ? "Outward" : "Inward";
 
 				frappe.require(path, function() {
 					new erpnext.SerialBatchPackageSelector(
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 56cc21c..6afbf01 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -286,6 +286,10 @@
 		}
 	},
 
+	add_serial_batch_bundle(frm, cdt, cdn) {
+		erpnext.utils.pick_serial_and_batch_bundle(frm, cdt, cdn, "Inward");
+	}
+
 });
 
 erpnext.stock.StockReconciliation = class StockReconciliation extends erpnext.stock.StockController {
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 4004c00..6ea27ed 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -193,7 +193,13 @@
 
 		def _changed(item):
 			if item.current_serial_and_batch_bundle:
-				self.calculate_difference_amount(item, frappe._dict({}))
+				bundle_data = frappe.get_all(
+					"Serial and Batch Bundle",
+					filters={"name": item.current_serial_and_batch_bundle},
+					fields=["total_qty as qty", "avg_rate as rate"],
+				)[0]
+
+				self.calculate_difference_amount(item, bundle_data)
 				return True
 
 			item_dict = get_stock_balance_for(
@@ -446,16 +452,17 @@
 
 			sl_entries.append(args)
 
-		args = self.get_sle_for_items(row)
-		args.update(
-			{
-				"actual_qty": row.qty,
-				"incoming_rate": row.valuation_rate,
-				"serial_and_batch_bundle": row.serial_and_batch_bundle,
-			}
-		)
+		if row.qty != 0:
+			args = self.get_sle_for_items(row)
+			args.update(
+				{
+					"actual_qty": row.qty,
+					"incoming_rate": row.valuation_rate,
+					"serial_and_batch_bundle": row.serial_and_batch_bundle,
+				}
+			)
 
-		sl_entries.append(args)
+			sl_entries.append(args)
 
 	def update_valuation_rate_for_serial_no(self):
 		for d in self.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 8738f4a..62d6e4c 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -103,7 +103,8 @@
   {
    "fieldname": "serial_no",
    "fieldtype": "Long Text",
-   "label": "Serial No"
+   "label": "Serial No",
+   "read_only": 1
   },
   {
    "fieldname": "column_break_11",
@@ -213,7 +214,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2023-05-27 17:35:31.026852",
+ "modified": "2023-06-15 11:45:55.808942",
  "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 a75c3b0..2c18f99 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -5,7 +5,7 @@
 from frappe import _, bold
 from frappe.model.naming import make_autoname
 from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, now, nowtime, today
+from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today
 
 from erpnext.stock.deprecated_serial_batch import (
 	DeprecatedBatchNoValuation,
@@ -79,9 +79,24 @@
 		self.set_serial_and_batch_bundle(sn_doc)
 
 	def validate_actual_qty(self, sn_doc):
+		link = get_link_to_form("Serial and Batch Bundle", sn_doc.name)
+
+		condition = {
+			"Inward": self.sle.actual_qty > 0,
+			"Outward": self.sle.actual_qty < 0,
+		}.get(sn_doc.type_of_transaction)
+
+		if not condition:
+			correct_type = "Inward"
+			if sn_doc.type_of_transaction == "Inward":
+				correct_type = "Outward"
+
+			msg = f"The type of transaction of Serial and Batch Bundle {link} is {bold(sn_doc.type_of_transaction)} but as per the Actual Qty {self.sle.actual_qty} for the item {bold(self.sle.item_code)} in the {self.sle.voucher_type} {self.sle.voucher_no} the type of transaction should be {bold(correct_type)}"
+			frappe.throw(_(msg), title=_("Incorrect Type of Transaction"))
+
 		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}"
+			msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {link} 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):