fix(Task): Do not create/schedule task after project end (#19184)

* fix: do not create/schedule task after project end

* fix: check difference between dates

* fix: check project date

* fix: task creation

* fix: tests
diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
index bb9045c..3e51933 100644
--- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
+++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
@@ -51,27 +51,25 @@
 		self.create_task(disease_doc.treatment_task, self.name, start_date)
 
 	def create_project(self, period, crop_tasks):
-		project = frappe.new_doc("Project")
-		project.update({
+		project = frappe.get_doc({
+			"doctype": "Project",
 			"project_name": self.title,
 			"expected_start_date": self.start_date,
 			"expected_end_date": add_days(self.start_date, period - 1)
-		})
-		project.insert()
+		}).insert()
 
 		return project.name
 
 	def create_task(self, crop_tasks, project_name, start_date):
 		for crop_task in crop_tasks:
-			task = frappe.new_doc("Task")
-			task.update({
+			frappe.get_doc({
+				"doctype": "Task",
 				"subject": crop_task.get("task_name"),
 				"priority": crop_task.get("priority"),
 				"project": project_name,
 				"exp_start_date": add_days(start_date, crop_task.get("start_day") - 1),
 				"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
-			})
-			task.insert()
+			}).insert()
 
 	def reload_linked_analysis(self):
 		linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 90e9f05..54fce8d 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -10,6 +10,7 @@
 from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate
 from frappe.utils.nestedset import NestedSet
 from frappe.desk.form.assign_to import close_all_assignments, clear
+from frappe.utils import date_diff
 
 class CircularReferenceError(frappe.ValidationError): pass
 class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass
@@ -28,16 +29,29 @@
 
 	def validate(self):
 		self.validate_dates()
+		self.validate_parent_project_dates()
 		self.validate_progress()
 		self.validate_status()
 		self.update_depends_on()
 
 	def validate_dates(self):
 		if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
-			frappe.throw(_("'Expected Start Date' can not be greater than 'Expected End Date'"))
+			frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \
+				frappe.bold("Expected End Date")))
 
 		if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
-			frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
+			frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \
+				frappe.bold("Actual End Date")))
+
+	def validate_parent_project_dates(self):
+		if not self.project or frappe.flags.in_test:
+			return
+
+		expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date"))
+
+		if expected_end_date:
+			validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected")
+			validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual")
 
 	def validate_status(self):
 		if self.status!=self.get_db_value("status") and self.status == "Completed":
@@ -255,3 +269,10 @@
 
 def on_doctype_update():
 	frappe.db.add_index("Task", ["lft", "rgt"])
+
+def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date):
+	if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
+		frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
+
+	if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
+		frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
\ No newline at end of file