Merge pull request #38962 from s-aga-r/FIX-38781

fix: use `Stock Qty` while getting `POS Reserved Qty`
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index c4492be..09912e9 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -23,6 +23,7 @@
   "against_voucher_type",
   "against_voucher",
   "voucher_type",
+  "voucher_subtype",
   "voucher_no",
   "voucher_detail_no",
   "project",
@@ -138,19 +139,19 @@
    "options": "DocType"
   },
   {
-    "fieldname": "against",
-    "fieldtype": "Text",
-    "in_filter": 1,
-    "label": "Against",
-    "oldfieldname": "against",
-    "oldfieldtype": "Text"
+   "fieldname": "against",
+   "fieldtype": "Text",
+   "in_filter": 1,
+   "label": "Against",
+   "oldfieldname": "against",
+   "oldfieldtype": "Text"
   },
   {
-    "fieldname": "against_link",
-    "fieldtype": "Dynamic Link",
-    "in_filter": 1,
-    "label": "Against",
-    "options": "against_type"
+   "fieldname": "against_link",
+   "fieldtype": "Dynamic Link",
+   "in_filter": 1,
+   "label": "Against",
+   "options": "against_type"
   },
   {
    "fieldname": "against_voucher_type",
@@ -294,13 +295,18 @@
    "fieldtype": "Currency",
    "label": "Credit Amount in Transaction Currency",
    "options": "transaction_currency"
+  },
+  {
+   "fieldname": "voucher_subtype",
+   "fieldtype": "Small Text",
+   "label": "Voucher Subtype"
   }
  ],
  "icon": "fa fa-list",
  "idx": 1,
  "in_create": 1,
  "links": [],
- "modified": "2023-11-08 12:20:23.031733",
+ "modified": "2023-12-18 15:38:14.006208",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "GL Entry",
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index f7dd29a..139f526 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -39,6 +39,8 @@
 		account: DF.Link | None
 		account_currency: DF.Link | None
 		against: DF.Text | None
+		against_link: DF.DynamicLink | None
+		against_type: DF.Link | None
 		against_voucher: DF.DynamicLink | None
 		against_voucher_type: DF.Link | None
 		company: DF.Link | None
@@ -66,6 +68,7 @@
 		transaction_exchange_rate: DF.Float
 		voucher_detail_no: DF.Data | None
 		voucher_no: DF.DynamicLink | None
+		voucher_subtype: DF.SmallText | None
 		voucher_type: DF.Link | None
 	# end: auto-generated types
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index cebd61a..215d8ec 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -163,6 +163,18 @@
 					}
 				})
 			}, __("Get Items From"));
+
+			if (!this.frm.doc.is_return) {
+				frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
+					if (value) {
+						this.frm.doc.items.forEach((item) => {
+							this.frm.fields_dict.items.grid.update_docfield_property(
+								"rate", "read_only", (item.purchase_receipt && item.pr_detail)
+							);
+						});
+					}
+				});
+			}
 		}
 		this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
 
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 7cad3ae..9cf4e4f 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -288,7 +288,6 @@
    "oldfieldname": "import_rate",
    "oldfieldtype": "Currency",
    "options": "currency",
-   "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)",
    "reqd": 1
   },
   {
@@ -919,7 +918,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-30 16:26:05.629780",
+ "modified": "2023-12-25 22:00:28.043555",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index b45ff60..4054dca 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -200,7 +200,7 @@
 		"""
 		select
 			name as gl_entry, posting_date, account, party_type, party,
-			voucher_type, voucher_no, {dimension_fields}
+			voucher_type, voucher_subtype, voucher_no, {dimension_fields}
 			cost_center, project, {transaction_currency_fields}
 			against_voucher_type, against_voucher, account_currency,
 			against_link, against, is_opening, creation {select_fields}
@@ -610,6 +610,12 @@
 	columns += [
 		{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
 		{
+			"label": _("Voucher Subtype"),
+			"fieldname": "voucher_subtype",
+			"fieldtype": "Data",
+			"width": 180,
+		},
+		{
 			"label": _("Voucher No"),
 			"fieldname": "voucher_no",
 			"fieldtype": "Dynamic Link",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index d88424b..febad18 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -874,6 +874,7 @@
 				"project": self.get("project"),
 				"post_net_value": args.get("post_net_value"),
 				"voucher_detail_no": args.get("voucher_detail_no"),
+				"voucher_subtype": self.get_voucher_subtype(),
 			}
 		)
 
@@ -929,6 +930,25 @@
 
 		return gl_dict
 
+	def get_voucher_subtype(self):
+		voucher_subtypes = {
+			"Journal Entry": "voucher_type",
+			"Payment Entry": "payment_type",
+			"Stock Entry": "stock_entry_type",
+			"Asset Capitalization": "entry_type",
+		}
+		if self.doctype in voucher_subtypes:
+			return self.get(voucher_subtypes[self.doctype])
+		elif self.doctype == "Purchase Receipt" and self.is_return:
+			return "Purchase Return"
+		elif self.doctype == "Delivery Note" and self.is_return:
+			return "Sales Return"
+		elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
+			return "Credit Note"
+		elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
+			return "Debit Note"
+		return self.doctype
+
 	def get_value_in_transaction_currency(self, account_currency, args, field):
 		if account_currency == self.get("currency"):
 			return args.get(field + "_in_account_currency")
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index e8d3542..5083873 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -218,6 +218,7 @@
    "options": "\nWork Order\nJob Card"
   },
   {
+   "default": "1",
    "fieldname": "conversion_rate",
    "fieldtype": "Float",
    "label": "Conversion Rate",
@@ -636,7 +637,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-08-07 11:38:08.152294",
+ "modified": "2023-12-26 19:34:08.159312",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index dd102b0..cd92263 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -305,6 +305,8 @@
 			frappe.throw(__("Select the Warehouse"));
 		}
 
+		frm.set_value("consider_minimum_order_qty", 0);
+
 		if (frm.doc.ignore_existing_ordered_qty) {
 			frm.events.get_items_for_material_requests(frm);
 		} else {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 49386c4..257b60c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -48,6 +48,7 @@
   "material_request_planning",
   "include_non_stock_items",
   "include_subcontracted_items",
+  "consider_minimum_order_qty",
   "include_safety_stock",
   "ignore_existing_ordered_qty",
   "column_break_25",
@@ -423,13 +424,19 @@
    "fieldtype": "Link",
    "label": "Sub Assembly Warehouse",
    "options": "Warehouse"
+  },
+  {
+   "default": "0",
+   "fieldname": "consider_minimum_order_qty",
+   "fieldtype": "Check",
+   "label": "Consider Minimum Order Qty"
   }
  ],
  "icon": "fa fa-calendar",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-11-03 14:08:11.928027",
+ "modified": "2023-12-26 16:31:13.740777",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 4b72a83..2bfd4be 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -67,6 +67,7 @@
 		combine_items: DF.Check
 		combine_sub_items: DF.Check
 		company: DF.Link
+		consider_minimum_order_qty: DF.Check
 		customer: DF.Link | None
 		for_warehouse: DF.Link | None
 		from_date: DF.Date | None
@@ -1211,7 +1212,14 @@
 
 
 def get_material_request_items(
-	row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
+	doc,
+	row,
+	sales_order,
+	company,
+	ignore_existing_ordered_qty,
+	include_safety_stock,
+	warehouse,
+	bin_dict,
 ):
 	total_qty = row["qty"]
 
@@ -1220,8 +1228,14 @@
 		required_qty = total_qty
 	elif total_qty > bin_dict.get("projected_qty", 0):
 		required_qty = total_qty - bin_dict.get("projected_qty", 0)
-	if required_qty > 0 and required_qty < row["min_order_qty"]:
+
+	if (
+		doc.get("consider_minimum_order_qty")
+		and required_qty > 0
+		and required_qty < row["min_order_qty"]
+	):
 		required_qty = row["min_order_qty"]
+
 	item_group_defaults = get_item_group_defaults(row.item_code, company)
 
 	if not row["purchase_uom"]:
@@ -1559,6 +1573,7 @@
 
 			if details.qty > 0:
 				items = get_material_request_items(
+					doc,
 					details,
 					sales_order,
 					company,
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index f86725d..cb99b88 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1499,6 +1499,29 @@
 		after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
 		self.assertAlmostEqual(after_qty, before_qty)
 
+	def test_min_order_qty_in_pp(self):
+		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+		from erpnext.stock.utils import get_or_make_bin
+
+		fg_item = make_item(properties={"is_stock_item": 1}).name
+		rm_item = make_item(properties={"is_stock_item": 1, "min_order_qty": 1000}).name
+
+		rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+		make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
+
+		pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1)
+
+		pln.for_warehouse = rm_warehouse
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			self.assertEqual(d.get("quantity"), 10.0)
+
+		pln.consider_minimum_order_qty = 1
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			self.assertEqual(d.get("quantity"), 1000.0)
+
 
 def create_production_plan(**args):
 	"""
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 084cca7..b92b02e 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -184,6 +184,12 @@
 				refresh_field("incentives",row.name,row.parentfield);
 			}
 
+			warehouse(doc, cdt, cdn) {
+				if (doc.docstatus === 0 && doc.is_return && !doc.return_against) {
+					frappe.model.set_value(cdt, cdn, "incoming_rate", 0.0);
+				}
+			}
+
 			toggle_editable_price_list_rate() {
 				var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
 				var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
index a58f403..40aa9ac 100644
--- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
+++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
@@ -3,11 +3,11 @@
 
 
 import frappe
-from frappe import _
+from frappe import _, qb
+from frappe.query_builder import Criterion
 
 from erpnext import get_default_company
 from erpnext.accounts.party import get_party_details
-from erpnext.stock.get_item_details import get_price_list_rate_for
 
 
 def execute(filters=None):
@@ -50,6 +50,42 @@
 	]
 
 
+def fetch_item_prices(
+	customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
+):
+	price_list_map = frappe._dict()
+	ip = qb.DocType("Item Price")
+	and_conditions = []
+	or_conditions = []
+	if items:
+		and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
+		and_conditions.append(ip.selling == True)
+
+		or_conditions.append(ip.customer == None)
+		or_conditions.append(ip.price_list == None)
+
+		if customer:
+			or_conditions.append(ip.customer == customer)
+
+		if price_list:
+			or_conditions.append(ip.price_list == price_list)
+
+		if selling_price_list:
+			or_conditions.append(ip.price_list == selling_price_list)
+
+		res = (
+			qb.from_(ip)
+			.select(ip.item_code, ip.price_list, ip.price_list_rate)
+			.where(Criterion.all(and_conditions))
+			.where(Criterion.any(or_conditions))
+			.run(as_dict=True)
+		)
+		for x in res:
+			price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
+
+	return price_list_map
+
+
 def get_data(filters=None):
 	data = []
 	customer_details = get_customer_details(filters)
@@ -59,9 +95,17 @@
 		"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
 	)
 	item_stock_map = {item.item_code: item.available for item in item_stock_map}
+	price_list_map = fetch_item_prices(
+		customer_details.customer,
+		customer_details.price_list,
+		customer_details.selling_price_list,
+		items,
+	)
 
 	for item in items:
-		price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
+		price_list_rate = price_list_map.get(
+			(item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
+		)
 		available_stock = item_stock_map.get(item.item_code)
 
 		data.append(
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 6c9d339..2cbccb0 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -88,6 +88,20 @@
 			}, __('Create'));
 		}
 
+		if (frm.doc.docstatus === 0) {
+			if (!frm.doc.is_return) {
+				frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
+					if (value) {
+						frm.doc.items.forEach((item) => {
+							frm.fields_dict.items.grid.update_docfield_property(
+								"rate", "read_only", (item.purchase_order && item.purchase_order_item)
+							);
+						});
+					}
+				});
+			}
+		}
+
 		frm.events.add_custom_buttons(frm);
 	},
 
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 7344d2a..9bd692a 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -359,7 +359,6 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_width": "100px",
-   "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
    "width": "100px"
   },
   {
@@ -1104,7 +1103,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-30 16:12:02.364608",
+ "modified": "2023-12-25 22:32:09.801965",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
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 37916e2..afb53fb 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
@@ -85,6 +85,7 @@
 	# end: auto-generated types
 
 	def validate(self):
+		self.set_batch_no()
 		self.validate_serial_and_batch_no()
 		self.validate_duplicate_serial_and_batch_no()
 		self.validate_voucher_no()
@@ -99,6 +100,26 @@
 		self.set_incoming_rate()
 		self.calculate_qty_and_amount()
 
+	def set_batch_no(self):
+		if self.has_serial_no and self.has_batch_no:
+			serial_nos = [d.serial_no for d in self.entries if d.serial_no]
+			has_no_batch = any(not d.batch_no for d in self.entries)
+			if not has_no_batch:
+				return
+
+			serial_no_batch = frappe._dict(
+				frappe.get_all(
+					"Serial No",
+					filters={"name": ("in", serial_nos)},
+					fields=["name", "batch_no"],
+					as_list=True,
+				)
+			)
+
+			for row in self.entries:
+				if not row.batch_no:
+					row.batch_no = serial_no_batch.get(row.serial_no)
+
 	def validate_serial_nos_inventory(self):
 		if not (self.has_serial_no and self.type_of_transaction == "Outward"):
 			return
@@ -1164,7 +1185,7 @@
 		doc.append(
 			"entries",
 			{
-				"qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1),
+				"qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1),
 				"warehouse": warehouse,
 				"batch_no": row.batch_no,
 				"serial_no": row.serial_no,
@@ -1192,7 +1213,7 @@
 		doc.append(
 			"entries",
 			{
-				"qty": (d.get("qty") or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
+				"qty": (flt(d.get("qty")) or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
 				"warehouse": warehouse or d.get("warehouse"),
 				"batch_no": d.get("batch_no"),
 				"serial_no": d.get("serial_no"),
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index e8d652e..6819968 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -171,7 +171,7 @@
 						},
 					)
 
-			if item_details.has_batch_no:
+			elif item_details.has_batch_no:
 				batch_nos_details = get_available_batches(
 					frappe._dict(
 						{
@@ -228,6 +228,9 @@
 
 	def set_new_serial_and_batch_bundle(self):
 		for item in self.items:
+			if not item.qty:
+				continue
+
 			if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle:
 				current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle)
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 1ec99bf..70e9fb2 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -865,6 +865,66 @@
 		sr1.load_from_db()
 		self.assertEqual(sr1.difference_amount, 10000)
 
+	def test_make_stock_zero_for_serial_batch_item(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+		serial_item = self.make_item(
+			properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DJJ.####"}
+		).name
+		batch_item = self.make_item(
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "BDJJ.####",
+				"create_new_batch": 1,
+			}
+		).name
+
+		serial_batch_item = self.make_item(
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "ADJJ.####",
+				"create_new_batch": 1,
+				"has_serial_no": 1,
+				"serial_no_series": "SN-ADJJ.####",
+			}
+		).name
+
+		warehouse = "_Test Warehouse - _TC"
+
+		for item_code in [serial_item, batch_item, serial_batch_item]:
+			make_stock_entry(
+				item_code=item_code,
+				target=warehouse,
+				qty=10,
+				basic_rate=100,
+			)
+
+			_reco = create_stock_reconciliation(
+				item_code=item_code,
+				warehouse=warehouse,
+				qty=0.0,
+			)
+
+			serial_batch_bundle = frappe.get_all(
+				"Stock Ledger Entry",
+				{"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
+				"serial_and_batch_bundle",
+			)
+
+			self.assertEqual(len(serial_batch_bundle), 1)
+
+			_reco.cancel()
+
+			serial_batch_bundle = frappe.get_all(
+				"Stock Ledger Entry",
+				{"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
+				"serial_and_batch_bundle",
+			)
+
+			self.assertEqual(len(serial_batch_bundle), 0)
+
 
 def create_batch_item_with_batch(item_name, batch_id):
 	batch_item_doc = create_item(item_name, is_stock_item=1)
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index 24650fd..7e03ac3 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -9,7 +9,7 @@
 from frappe.query_builder.functions import Sum
 from frappe.utils import cint, flt
 
-from erpnext.stock.utils import get_or_make_bin
+from erpnext.stock.utils import get_or_make_bin, get_stock_balance
 
 
 class StockReservationEntry(Document):
@@ -151,7 +151,7 @@
 		"""Validates `Reserved Qty` when `Reservation Based On` is `Qty`."""
 
 		if self.reservation_based_on == "Qty":
-			self.validate_with_max_reserved_qty(self.reserved_qty)
+			self.validate_with_allowed_qty(self.reserved_qty)
 
 	def auto_reserve_serial_and_batch(self, based_on: str = None) -> None:
 		"""Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`."""
@@ -324,7 +324,7 @@
 				frappe.throw(msg)
 
 			# Should be called after validating Serial and Batch Nos.
-			self.validate_with_max_reserved_qty(qty_to_be_reserved)
+			self.validate_with_allowed_qty(qty_to_be_reserved)
 			self.db_set("reserved_qty", qty_to_be_reserved)
 
 	def update_reserved_qty_in_voucher(
@@ -429,7 +429,7 @@
 			msg = _("Stock Reservation Entry cannot be updated as it has been delivered.")
 			frappe.throw(msg)
 
-	def validate_with_max_reserved_qty(self, qty_to_be_reserved: float) -> None:
+	def validate_with_allowed_qty(self, qty_to_be_reserved: float) -> None:
 		"""Validates `Reserved Qty` with `Max Reserved Qty`."""
 
 		self.db_set(
@@ -448,12 +448,12 @@
 			)
 			voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
 
-		max_reserved_qty = min(
+		allowed_qty = min(
 			self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)
 		)
 
-		if max_reserved_qty <= 0 and self.voucher_type == "Sales Order":
-			msg = _("Item {0} is already delivered for Sales Order {1}.").format(
+		if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0:
+			msg = _("Item {0} is already reserved/delivered against Sales Order {1}.").format(
 				frappe.bold(self.item_code), frappe.bold(self.voucher_no)
 			)
 
@@ -463,19 +463,33 @@
 			else:
 				frappe.throw(msg)
 
-		if qty_to_be_reserved > max_reserved_qty:
+		if qty_to_be_reserved > allowed_qty:
+			actual_qty = get_stock_balance(self.item_code, self.warehouse)
 			msg = """
-				Cannot reserve more than Max Reserved Qty {0} {1}.<br /><br />
-				The <b>Max Reserved Qty</b> is calculated as follows:<br />
+				Cannot reserve more than Allowed Qty {0} {1} for Item {2} against {3} {4}.<br /><br />
+				The <b>Allowed Qty</b> is calculated as follows:<br />
 				<ul>
-					<li><b>Available Qty To Reserve</b> = (Actual Stock Qty - Reserved Stock Qty)</li>
-					<li><b>Voucher Qty</b> = Voucher Item Qty</li>
-					<li><b>Delivered Qty</b> = Qty delivered against the Voucher Item</li>
-					<li><b>Total Reserved Qty</b> = Qty reserved against the Voucher Item</li>
-					<li><b>Max Reserved Qty</b> = Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))</li>
+					<li>Actual Qty [Available Qty at Warehouse] = {5}</li>
+					<li>Reserved Stock [Ignore current SRE] = {6}</li>
+					<li>Available Qty To Reserve [Actual Qty - Reserved Stock] = {7}</li>
+					<li>Voucher Qty [Voucher Item Qty] = {8}</li>
+					<li>Delivered Qty [Qty delivered against the Voucher Item] = {9}</li>
+					<li>Total Reserved Qty [Qty reserved against the Voucher Item] = {10}</li>
+					<li>Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {11}</li>
 				</ul>
 			""".format(
-				frappe.bold(max_reserved_qty), self.stock_uom
+				frappe.bold(allowed_qty),
+				self.stock_uom,
+				frappe.bold(self.item_code),
+				self.voucher_type,
+				frappe.bold(self.voucher_no),
+				actual_qty,
+				actual_qty - self.available_qty,
+				self.available_qty,
+				self.voucher_qty,
+				voucher_delivered_qty,
+				total_reserved_qty,
+				allowed_qty,
 			)
 			frappe.throw(msg)
 
@@ -509,7 +523,6 @@
 	"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination."""
 
 	from erpnext.stock.doctype.batch.batch import get_batch_qty
-	from erpnext.stock.utils import get_stock_balance
 
 	if batch_no:
 		return get_batch_qty(
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js
index 6872741..2b075e2 100644
--- a/erpnext/stock/report/reserved_stock/reserved_stock.js
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.js
@@ -149,34 +149,36 @@
 	formatter: (value, row, column, data, default_formatter) => {
 		value = default_formatter(value, row, column, data);
 
-		if (column.fieldname == "status") {
-			switch (data.status) {
-				case "Partially Reserved":
-					value = "<span style='color:orange'>" + value + "</span>";
-					break;
-				case "Reserved":
-					value = "<span style='color:blue'>" + value + "</span>";
-					break;
-				case "Partially Delivered":
-					value = "<span style='color:purple'>" + value + "</span>";
-					break;
-				case "Delivered":
-					value = "<span style='color:green'>" + value + "</span>";
-					break;
+		if (data) {
+			if (column.fieldname == "status") {
+				switch (data.status) {
+					case "Partially Reserved":
+						value = "<span style='color:orange'>" + value + "</span>";
+						break;
+					case "Reserved":
+						value = "<span style='color:blue'>" + value + "</span>";
+						break;
+					case "Partially Delivered":
+						value = "<span style='color:purple'>" + value + "</span>";
+						break;
+					case "Delivered":
+						value = "<span style='color:green'>" + value + "</span>";
+						break;
+				}
 			}
-		}
-		else if (column.fieldname == "delivered_qty") {
-			if (data.delivered_qty > 0) {
-				if (data.reserved_qty > data.delivered_qty) {
-					value = "<span style='color:blue'>" + value + "</span>";
+			else if (column.fieldname == "delivered_qty") {
+				if (data.delivered_qty > 0) {
+					if (data.reserved_qty > data.delivered_qty) {
+						value = "<span style='color:blue'>" + value + "</span>";
+					}
+					else {
+						value = "<span style='color:green'>" + value + "</span>";
+					}
 				}
 				else {
-					value = "<span style='color:green'>" + value + "</span>";
+					value = "<span style='color:red'>" + value + "</span>";
 				}
 			}
-			else {
-				value = "<span style='color:red'>" + value + "</span>";
-			}
 		}
 
 		return value;
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 2745d4d..d05d0d9 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -103,6 +103,7 @@
 Actual Qty {0} / Waiting Qty {1},Tatsächliche Menge {0} / Wartezeit {1},
 Actual Qty: Quantity available in the warehouse.,Tatsächliche Menge: Menge verfügbar im Lager.,
 Actual qty in stock,Tatsächliche Menge auf Lager,
+Actual Time (in Hours via Time Sheet), IST Zeit (in Stunden aus Zeiterfassung),
 Actual type tax cannot be included in Item rate in row {0},Tatsächliche Steuerart kann nicht im Artikelpreis in Zeile {0} beinhaltet sein,
 Add,Hinzufügen,
 Add / Edit Prices,Preise hinzufügen / bearbeiten,