From ed1a1099cb53d336413b1e4df95cb5d46bddd5a2 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Tue, 4 Nov 2025 08:30:54 +0000 Subject: [PATCH 1/3] fix: validate is_group for parent task --- erpnext/projects/doctype/task/task.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 55be319d23e..7177216b99d 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -17,6 +17,10 @@ class CircularReferenceError(frappe.ValidationError): pass +class ParentIsGroupError(frappe.ValidationError): + pass + + class Task(NestedSet): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -84,6 +88,7 @@ class Task(NestedSet): self.validate_dependencies_for_template_task() self.validate_completed_on() self.set_default_end_date_if_missing() + self.validate_parent_is_group() def validate_dates(self): self.validate_from_to_dates("exp_start_date", "exp_end_date") @@ -172,6 +177,14 @@ class Task(NestedSet): if self.completed_on and getdate(self.completed_on) > getdate(): frappe.throw(_("Completed On cannot be greater than Today")) + def validate_parent_is_group(self): + if self.parent_task: + if not frappe.db.get_value("Task", self.parent_task, "is_group"): + parent_task_format = f"""{self.parent_task}""" + frappe.throw( + _("Parent Task {0} must be a Group Task").format(parent_task_format), ParentIsGroupError + ) + def update_depends_on(self): depends_on_tasks = "" for d in self.depends_on: From 291f0c71613236cdbc92aff9979d13c4bca23817 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Tue, 4 Nov 2025 08:31:41 +0000 Subject: [PATCH 2/3] test: add test for parent task is_group validation --- erpnext/projects/doctype/task/test_task.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 7a93585e832..d36272f9816 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -6,7 +6,7 @@ import frappe from frappe.tests import IntegrationTestCase from frappe.utils import add_days, getdate, nowdate -from erpnext.projects.doctype.task.task import CircularReferenceError +from erpnext.projects.doctype.task.task import CircularReferenceError, ParentIsGroupError from erpnext.tests.utils import ERPNextTestSuite @@ -119,6 +119,20 @@ class TestTask(ERPNextTestSuite): self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue") + def test_parent_task_must_be_group(self): + parent_task = create_task( + subject="_Test Parent Task Non Group", + is_group=0, + ) + + child_task = create_task( + subject="_Test Child Task", + parent_task=parent_task.name, + save=False, + ) + + self.assertRaises(ParentIsGroupError, child_task.save) + def create_task( subject, From 4cf02b4d78980c34b0de66aaac169681a4651577 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Thu, 6 Nov 2025 11:22:58 +0000 Subject: [PATCH 3/3] refactor(task): use get_link_to_form for validation error messages --- erpnext/projects/doctype/task/task.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 7177216b99d..cb971f13992 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -163,15 +163,21 @@ class Task(NestedSet): def validate_parent_template_task(self): if self.parent_task: if not frappe.db.get_value("Task", self.parent_task, "is_template"): - parent_task_format = f"""{self.parent_task}""" - frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format)) + frappe.throw( + _("Parent Task {0} is not a Template Task").format( + get_link_to_form("Task", self.parent_task) + ) + ) def validate_depends_on_tasks(self): if self.depends_on: for task in self.depends_on: if not frappe.db.get_value("Task", task.task, "is_template"): - dependent_task_format = f"""{task.task}""" - frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) + frappe.throw( + _("Dependent Task {0} is not a Template Task").format( + get_link_to_form("Task", task.task) + ) + ) def validate_completed_on(self): if self.completed_on and getdate(self.completed_on) > getdate(): @@ -180,9 +186,11 @@ class Task(NestedSet): def validate_parent_is_group(self): if self.parent_task: if not frappe.db.get_value("Task", self.parent_task, "is_group"): - parent_task_format = f"""{self.parent_task}""" frappe.throw( - _("Parent Task {0} must be a Group Task").format(parent_task_format), ParentIsGroupError + _("Parent Task {0} must be a Group Task").format( + get_link_to_form("Task", self.parent_task) + ), + ParentIsGroupError, ) def update_depends_on(self):