Merge pull request #27735 from GangaManoj/fix-so-cancellation-message

fix: Sales Order cancellation message
diff --git a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
index a0c1a33..6a9e70a 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
@@ -3,7 +3,39 @@
 
 import unittest
 
+import frappe
+from frappe.utils.data import today
+
 # test_records = frappe.get_test_records('Maintenance Visit')
 
 class TestMaintenanceVisit(unittest.TestCase):
 	pass
+
+def make_maintenance_visit():
+	mv = frappe.new_doc("Maintenance Visit")
+	mv.company = "_Test Company"
+	mv.customer = "_Test Customer"
+	mv.mntc_date = today()
+	mv.completion_status = "Partially Completed"
+
+	sales_person = make_sales_person("Dwight Schrute")
+
+	mv.append("purposes", {
+		"item_code": "_Test Item",
+		"sales_person": "Sales Team",
+		"description": "Test Item",
+		"work_done": "Test Work Done",
+		"service_person": sales_person.name
+	})
+	mv.insert(ignore_permissions=True)
+
+	return mv
+
+def make_sales_person(name):
+	sales_person = frappe.get_doc({
+		'doctype': "Sales Person",
+		'sales_person_name': name
+	})
+	sales_person.insert(ignore_if_duplicate = True)
+
+	return sales_person
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 8cb0f29..47b8ebd 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -223,60 +223,15 @@
 			check_credit_limit(self.customer, self.company)
 
 	def check_nextdoc_docstatus(self):
-		# Checks Delivery Note
-		submit_dn = frappe.db.sql_list("""
-			select t1.name
-			from `tabDelivery Note` t1,`tabDelivery Note Item` t2
-			where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1""", self.name)
-
-		if submit_dn:
-			submit_dn = [get_link_to_form("Delivery Note", dn) for dn in submit_dn]
-			frappe.throw(_("Delivery Notes {0} must be cancelled before cancelling this Sales Order")
-				.format(", ".join(submit_dn)))
-
-		# Checks Sales Invoice
-		submit_rv = frappe.db.sql_list("""select t1.name
+		linked_invoices = frappe.db.sql_list("""select distinct t1.name
 			from `tabSales Invoice` t1,`tabSales Invoice Item` t2
-			where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus < 2""",
+			where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 0""",
 			self.name)
 
-		if submit_rv:
-			submit_rv = [get_link_to_form("Sales Invoice", si) for si in submit_rv]
-			frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order")
-				.format(", ".join(submit_rv)))
-
-		#check maintenance schedule
-		submit_ms = frappe.db.sql_list("""
-			select t1.name
-			from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2
-			where t2.parent=t1.name and t2.sales_order = %s and t1.docstatus = 1""", self.name)
-
-		if submit_ms:
-			submit_ms = [get_link_to_form("Maintenance Schedule", ms) for ms in submit_ms]
-			frappe.throw(_("Maintenance Schedule {0} must be cancelled before cancelling this Sales Order")
-				.format(", ".join(submit_ms)))
-
-		# check maintenance visit
-		submit_mv = frappe.db.sql_list("""
-			select t1.name
-			from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2
-			where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1""",self.name)
-
-		if submit_mv:
-			submit_mv = [get_link_to_form("Maintenance Visit", mv) for mv in submit_mv]
-			frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order")
-				.format(", ".join(submit_mv)))
-
-		# check work order
-		pro_order = frappe.db.sql_list("""
-			select name
-			from `tabWork Order`
-			where sales_order = %s and docstatus = 1""", self.name)
-
-		if pro_order:
-			pro_order = [get_link_to_form("Work Order", po) for po in pro_order]
-			frappe.throw(_("Work Order {0} must be cancelled before cancelling this Sales Order")
-				.format(", ".join(pro_order)))
+		if linked_invoices:
+			linked_invoices = [get_link_to_form("Sales Invoice", si) for si in linked_invoices]
+			frappe.throw(_("Sales Invoice {0} must be deleted before cancelling this Sales Order")
+				.format(", ".join(linked_invoices)))
 
 	def check_modified_date(self):
 		mod_db = frappe.db.get_value("Sales Order", self.name, "modified")
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 644c0ac..2a0752e 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -10,6 +10,12 @@
 from frappe.utils import add_days, flt, getdate, nowdate
 
 from erpnext.controllers.accounts_controller import update_child_qty_rate
+from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
+	make_maintenance_schedule,
+)
+from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import (
+	make_maintenance_visit,
+)
 from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
 from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
 from erpnext.selling.doctype.sales_order.sales_order import (
@@ -1278,6 +1284,72 @@
 
 		self.assertRaises(frappe.ValidationError, so.cancel)
 
+	def test_so_cancellation_after_si_submission(self):
+		"""
+			Test to check if Sales Order gets cancelled when linked Sales Invoice has been Submitted
+			Expected result: Sales Order should not get cancelled
+		"""
+		so = make_sales_order()
+		so.submit()
+		si = make_sales_invoice(so.name)
+		si.submit()
+
+		so.load_from_db()
+		self.assertRaises(frappe.LinkExistsError, so.cancel)
+
+	def test_so_cancellation_after_dn_submission(self):
+		"""
+			Test to check if Sales Order gets cancelled when linked Delivery Note has been Submitted
+			Expected result: Sales Order should not get cancelled
+		"""
+		so = make_sales_order()
+		so.submit()
+		dn = make_delivery_note(so.name)
+		dn.submit()
+
+		so.load_from_db()
+		self.assertRaises(frappe.LinkExistsError, so.cancel)
+
+	def test_so_cancellation_after_maintenance_schedule_submission(self):
+		"""
+			Expected result: Sales Order should not get cancelled
+		"""
+		so = make_sales_order()
+		so.submit()
+		ms = make_maintenance_schedule()
+		ms.items[0].sales_order = so.name
+		ms.submit()
+
+		so.load_from_db()
+		self.assertRaises(frappe.LinkExistsError, so.cancel)
+
+	def test_so_cancellation_after_maintenance_visit_submission(self):
+		"""
+			Expected result: Sales Order should not get cancelled
+		"""
+		so = make_sales_order()
+		so.submit()
+		mv = make_maintenance_visit()
+		mv.purposes[0].prevdoc_doctype = "Sales Order"
+		mv.purposes[0].prevdoc_docname = so.name
+		mv.submit()
+
+		so.load_from_db()
+		self.assertRaises(frappe.LinkExistsError, so.cancel)
+
+	def test_so_cancellation_after_work_order_submission(self):
+		"""
+			Expected result: Sales Order should not get cancelled
+		"""
+		from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+
+		so = make_sales_order(item_code="_Test FG Item", qty=10)
+		so.submit()
+		make_wo_order_test_record(sales_order=so.name)
+
+		so.load_from_db()
+		self.assertRaises(frappe.LinkExistsError, so.cancel)
+
 	def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
 			create_payment_terms_template,