feat: Optionally allow rejected quality inspection on submission
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 35097b9..3112fa7 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -356,42 +356,68 @@
 		}, update_modified)
 
 	def validate_inspection(self):
-		'''Checks if quality inspection is set for Items that require inspection.
-		On submit, throw an exception'''
-		inspection_required_fieldname = None
-		if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
-			inspection_required_fieldname = "inspection_required_before_purchase"
-		elif self.doctype in ["Delivery Note", "Sales Invoice"]:
-			inspection_required_fieldname = "inspection_required_before_delivery"
+		"""Checks if quality inspection is set/ is valid for Items that require inspection."""
+		inspection_fieldname_map = {
+			"Purchase Receipt": "inspection_required_before_purchase",
+			"Purchase Invoice": "inspection_required_before_purchase",
+			"Sales Invoice": "inspection_required_before_delivery",
+			"Delivery Note": "inspection_required_before_delivery"
+		}
+		inspection_required_fieldname = inspection_fieldname_map.get(self.doctype)
 
+		# return if inspection is not required on document level
 		if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or
 			(self.doctype == "Stock Entry" and not self.inspection_required) or
 			(self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)):
 				return
 
-		for d in self.get('items'):
-			qa_required = False
-			if (inspection_required_fieldname and not d.quality_inspection and
-				frappe.db.get_value("Item", d.item_code, inspection_required_fieldname)):
-				qa_required = True
-			elif self.doctype == "Stock Entry" and not d.quality_inspection and d.t_warehouse:
-				qa_required = True
-			if self.docstatus == 1 and d.quality_inspection:
-				qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection)
-				if qa_doc.docstatus == 0:
-					link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
-					frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)
+		for row in self.get('items'):
+			qi_required = False
+			if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)):
+				qi_required = True
+			elif self.doctype == "Stock Entry" and row.t_warehouse:
+				qi_required = True # inward stock needs inspection
 
-				if qa_doc.status != 'Accepted':
-					frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
-						.format(d.idx, d.item_code), QualityInspectionRejectedError)
-			elif qa_required :
-				action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted
-				if self.docstatus==1 and action == 'Stop':
-					frappe.throw(_("Quality Inspection required for Item {0} to submit").format(frappe.bold(d.item_code)),
-						exc=QualityInspectionRequiredError)
-				else:
-					frappe.msgprint(_("Create Quality Inspection for Item {0}").format(frappe.bold(d.item_code)))
+			if qi_required: # validate row only if inspection is required on item level
+				self.validate_qi_presence(row)
+				if self.docstatus == 1:
+					self.validate_qi_submission(row)
+					self.validate_qi_rejection(row)
+
+	def validate_qi_presence(self, row):
+		"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
+		if not row.quality_inspection:
+			msg = _(f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}")
+			if self.docstatus == 1:
+				frappe.throw(msg, title=_("Inspection Required"), exc=QualityInspectionRequiredError)
+			else:
+				frappe.msgprint(msg, title=_("Inspection Required"), indicator="blue")
+
+	def validate_qi_submission(self, row):
+		"""Check if QI is submitted on row level, during submission"""
+		action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted or "Stop"
+		qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
+
+		if not qa_docstatus == 1:
+			link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
+			msg = _(f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}")
+			if action == "Stop":
+				frappe.throw(msg, title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
+			else:
+				frappe.msgprint(msg, alert=True)
+
+	def validate_qi_rejection(self, row):
+		"""Check if QI is rejected on row level, during submission"""
+		action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_rejected or "Stop"
+		qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status")
+
+		if qa_status == "Rejected":
+			link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
+			msg = _(f"Row #{row.idx}: Quality Inspection was rejected for item {row.item_code}")
+			if action == "Stop":
+				frappe.throw(msg, title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
+			else:
+				frappe.msgprint(msg, alert=True, indicator="orange")
 
 	def update_blanket_order(self):
 		blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order]))
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 864ff48..a007389 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -307,6 +307,7 @@
    "fieldname": "quality_inspection",
    "fieldtype": "Link",
    "label": "Quality Inspection",
+   "no_copy": 1,
    "options": "Quality Inspection"
   },
   {
@@ -548,7 +549,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-11 13:47:50.158754",
+ "modified": "2021-06-21 16:03:18.834880",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index cf5d98d..d07e26b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -23,7 +23,10 @@
   "allow_negative_stock",
   "show_barcode_field",
   "clean_description_html",
+  "quality_inspection_settings_section",
   "action_if_quality_inspection_is_not_submitted",
+  "column_break_21",
+  "action_if_quality_inspection_is_rejected",
   "section_break_7",
   "automatically_set_serial_nos_based_on_fifo",
   "set_qty_in_transactions_based_on_serial_no_input",
@@ -264,6 +267,22 @@
   {
    "fieldname": "column_break_31",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "quality_inspection_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Quality Inspection Settings"
+  },
+  {
+   "fieldname": "column_break_21",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "Stop",
+   "fieldname": "action_if_quality_inspection_is_rejected",
+   "fieldtype": "Select",
+   "label": "Action If Quality Inspection Is Rejected",
+   "options": "Stop\nWarn"
   }
  ],
  "icon": "icon-cog",
@@ -271,7 +290,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-04-30 17:27:42.709231",
+ "modified": "2021-06-21 16:17:42.159829",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",