refactor: Consolidate duplicate zero-quantity transaction Items checks.
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index eea8cd5..e4479ef 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -65,6 +65,7 @@
 	def validate(self):
 		self.validate_duplicate_supplier()
 		self.validate_supplier_list()
+		super(RequestforQuotation, self).validate_qty_is_not_zero()
 		validate_for_items(self)
 		super(RequestforQuotation, self).set_qty_as_per_stock_uom()
 		self.update_email_id()
diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py
index 8b7b694..61e5e6a 100644
--- a/erpnext/buying/utils.py
+++ b/erpnext/buying/utils.py
@@ -42,16 +42,8 @@
 
 
 def validate_for_items(doc) -> None:
-	from erpnext.controllers.accounts_controller import InvalidQtyError
-
 	items = []
 	for d in doc.get("items"):
-		if not d.qty:
-			if doc.doctype == "Purchase Receipt" and d.rejected_qty:
-				continue
-			message = _("Please enter quantity for Item {0}").format(d.item_code)
-			frappe.throw(message, InvalidQtyError)
-
 		set_stock_levels(row=d)  # update with latest quantities
 		item = validate_item_and_get_basic_data(row=d)
 		validate_stock_item_warehouse(row=d, item=item)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1b8f66c..09bcea9 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -915,13 +915,15 @@
 			return flt(args.get(field, 0) / self.get("conversion_rate", 1))
 
 	def validate_qty_is_not_zero(self):
-		if self.doctype == "Purchase Receipt":
-			return
-
 		for item in self.items:
+			if self.doctype == "Purchase Receipt" and item.rejected_qty:
+				continue
+
 			if not flt(item.qty):
 				frappe.throw(
-					msg=_("Row #{0}: Item quantity cannot be zero").format(item.idx),
+					msg=_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
+						item.idx, frappe.bold(item.item_code)
+					),
 					title=_("Invalid Quantity"),
 					exc=InvalidQtyError,
 				)
@@ -3019,7 +3021,7 @@
 	def validate_quantity(child_item, new_data):
 		if not flt(new_data.get("qty")):
 			frappe.throw(
-				_("Row # {0}: Quantity for Item {1} cannot be zero").format(
+				_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
 					new_data.get("idx"), frappe.bold(new_data.get("item_code"))
 				),
 				title=_("Invalid Qty"),
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index d6e3ee2..3bc054f 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -7,7 +7,7 @@
 from frappe.utils import cint, flt, get_link_to_form, nowtime
 
 from erpnext.accounts.party import render_address
-from erpnext.controllers.accounts_controller import InvalidQtyError, get_taxes_and_charges
+from erpnext.controllers.accounts_controller import get_taxes_and_charges
 from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
 from erpnext.controllers.stock_controller import StockController
 from erpnext.stock.doctype.item.item import set_item_default
@@ -295,10 +295,6 @@
 	def get_item_list(self):
 		il = []
 		for d in self.get("items"):
-			if d.qty is None:
-				message = _("Row {0}: Qty is mandatory").format(d.idx)
-				frappe.throw(message, InvalidQtyError)
-
 			if self.has_product_bundle(d.item_code):
 				for p in self.get("packed_items"):
 					if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 37a80be..e694898 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -24,7 +24,6 @@
 
 import erpnext
 from erpnext.accounts.general_ledger import process_gl_map
-from erpnext.controllers.accounts_controller import InvalidQtyError
 from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
 from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, validate_bom_no
 from erpnext.setup.doctype.brand.brand import get_brand_defaults
@@ -389,10 +388,8 @@
 				frappe.delete_doc("Stock Entry", d.name)
 
 	def set_transfer_qty(self):
+		self.validate_qty_is_not_zero()
 		for item in self.get("items"):
-			if not flt(item.qty):
-				message = _("Row {0}: Qty is mandatory").format(item.idx)
-				frappe.throw(message, InvalidQtyError, title=_("Zero quantity"))
 			if not flt(item.conversion_factor):
 				frappe.throw(_("Row {0}: UOM Conversion Factor is mandatory").format(item.idx))
 			item.transfer_qty = flt(