From 7369df6bf5e93325562147c16aaafa0d53101207 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 00:36:56 +0200 Subject: [PATCH 01/11] fix: create Project from Template set Project Type and Start Date --- erpnext/projects/doctype/project/project.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index ff54fd084d0a..99fff00fc2ad 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -105,15 +105,20 @@ def copy_from_template(self): Copy tasks from template """ if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): + # this method is called by after_insert, so if data is set it need to be persisted + need_save = False + # has a template, and no loaded tasks, so lets create if not self.expected_start_date: # project starts today self.expected_start_date = today() + need_save = True template = frappe.get_doc("Project Template", self.project_template) if not self.project_type: self.project_type = template.project_type + need_save = True # create tasks from template project_tasks = [] @@ -124,6 +129,9 @@ def copy_from_template(self): task = self.create_task_from_template(template_task_details) project_tasks.append(task) + if need_save: + self.save(ignore_permissions=True, ignore_version=True) + self.dependency_mapping(tmp_task_details, project_tasks) def create_task_from_template(self, task_details): From c8d5c4e43747ffb0be2961976c373684ff5b41e6 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 08:31:19 +0200 Subject: [PATCH 02/11] fix: create Project from Template set Project Type and Start Date --- erpnext/projects/doctype/project/project.py | 43 +++++++++++---------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 99fff00fc2ad..cd3ca845425e 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -93,33 +93,21 @@ def before_print(self, settings=None): def validate(self): if not self.is_new(): - self.copy_from_template() + self.copy_from_template_data() + self.copy_from_template_task() self.send_welcome_email() self.update_costing() self.update_percent_complete() self.validate_from_to_dates("expected_start_date", "expected_end_date") self.validate_from_to_dates("actual_start_date", "actual_end_date") - def copy_from_template(self): + def copy_from_template_task(self): """ Copy tasks from template """ + # has a template, and no loaded tasks, so lets create if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): - # this method is called by after_insert, so if data is set it need to be persisted - need_save = False - - # has a template, and no loaded tasks, so lets create - if not self.expected_start_date: - # project starts today - self.expected_start_date = today() - need_save = True - - template = frappe.get_doc("Project Template", self.project_template) - - if not self.project_type: - self.project_type = template.project_type - need_save = True - + template = frappe.get_cached_doc("Project Template", self.project_template) # create tasks from template project_tasks = [] tmp_task_details = [] @@ -129,9 +117,6 @@ def copy_from_template(self): task = self.create_task_from_template(template_task_details) project_tasks.append(task) - if need_save: - self.save(ignore_permissions=True, ignore_version=True) - self.dependency_mapping(tmp_task_details, project_tasks) def create_task_from_template(self, task_details): @@ -154,6 +139,21 @@ def create_task_from_template(self, task_details): ) ).insert() + def copy_from_template_data(self): + """ + Copy data from template + """ + if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): + # has a template, and no loaded tasks, so lets create + if not self.expected_start_date: + # project starts today + self.expected_start_date = today() + + template = frappe.get_doc("Project Template", self.project_template) + + if not self.project_type: + self.project_type = template.project_type + def calculate_start_date(self, task_details): self.start_date = add_days(self.expected_start_date, task_details.start) self.start_date = self.update_if_holiday(self.start_date) @@ -212,6 +212,9 @@ def update_project(self): self.update_costing() self.db_update() + def before_insert(self): + self.copy_from_template_data() + def after_insert(self): self.copy_from_template() if self.sales_order: From 4fda0fd66f9f766370358ff1416c46938a1dc54d Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 08:33:17 +0200 Subject: [PATCH 03/11] fix: create Project from Template set Project Type and Start Date --- erpnext/projects/doctype/project/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index cd3ca845425e..1bea06e3b094 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -216,7 +216,7 @@ def before_insert(self): self.copy_from_template_data() def after_insert(self): - self.copy_from_template() + self.copy_from_template_task() if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) From 03ec44a7c170c05f6efa23d985a86e25f54b8331 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 08:42:56 +0200 Subject: [PATCH 04/11] fix: create Project from Template set Project Type and Start Date --- erpnext/projects/doctype/project/test_project.py | 11 ++++++++++- .../doctype/project_template/test_project_template.py | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 1b7460f7a2a0..0cfa01ba485f 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -3,7 +3,7 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, getdate, nowdate +from frappe.utils import add_days, getdate, nowdate, today from erpnext.projects.doctype.project_template.test_project_template import make_project_template from erpnext.projects.doctype.task.test_task import create_task @@ -39,6 +39,9 @@ def test_project_with_template_having_no_parent_and_depend_tasks(self): order_by="creation asc", ) + self.assertEqual(project.project_type, "Internal") + self.assertEqual(project.expected_start_date, today()) + self.assertEqual(tasks[0].priority, "High") self.assertEqual(tasks[0].subject, "Test Template Task with No Parent and Dependency") self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 5, 3)) @@ -90,6 +93,9 @@ def test_project_template_having_parent_child_tasks(self): order_by="creation asc", ) + self.assertEqual(project.project_type, "Internal") + self.assertEqual(project.expected_start_date, today()) + self.assertEqual(tasks[0].subject, "Test Template Task Parent") self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10)) @@ -133,6 +139,9 @@ def test_project_template_having_dependent_tasks(self): order_by="creation asc", ) + self.assertEqual(project.project_type, "Internal") + self.assertEqual(project.expected_start_date, today()) + self.assertEqual(tasks[1].subject, "Test Template Task with Dependency") self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 2, 2)) self.assertTrue(tasks[1].depends_on_tasks.find(tasks[0].name) >= 0) diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 382ffd5aa4cf..65f4cbd22ff0 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -20,7 +20,9 @@ def make_project_template(project_template_name, project_tasks=None): create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), ] - doc = frappe.get_doc(dict(doctype="Project Template", name=project_template_name)) + doc = frappe.get_doc( + dict(doctype="Project Template", name=project_template_name, project_type="Internal") + ) for task in project_tasks: doc.append("tasks", {"task": task.name}) doc.insert() From 68cde92977551d8088a488d5d842f5c13289da47 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 12:57:43 +0200 Subject: [PATCH 05/11] fix: create Project from Template set Project Type and Start Date --- .../doctype/project_template/test_project_template.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 65f4cbd22ff0..58516cf48cf4 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -20,9 +20,7 @@ def make_project_template(project_template_name, project_tasks=None): create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), ] - doc = frappe.get_doc( - dict(doctype="Project Template", name=project_template_name, project_type="Internal") - ) + doc = frappe.get_doc(doctype="Project Template", name=project_template_name, project_type="Internal") for task in project_tasks: doc.append("tasks", {"task": task.name}) doc.insert() From 3a02602d034e017e10265c477b2dce85349ab2c6 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 13:01:54 +0200 Subject: [PATCH 06/11] fix: create Project from Template set Project Type and Start Date --- .../doctype/project_template/test_project_template.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 58516cf48cf4..08e512ca5371 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -20,7 +20,9 @@ def make_project_template(project_template_name, project_tasks=None): create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), ] - doc = frappe.get_doc(doctype="Project Template", name=project_template_name, project_type="Internal") + doc = frappe.get_doc( + {"doctype": "Project Template", "name": "project_template_name", "project_type": "Internal"} + ) for task in project_tasks: doc.append("tasks", {"task": task.name}) doc.insert() From b3676881c4268e65716db36d65525c584a824f9b Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 3 May 2024 13:18:10 +0200 Subject: [PATCH 07/11] fix: create Project from Template set Project Type and Start Date --- .../projects/doctype/project_template/test_project_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 08e512ca5371..d70dd2f5cda1 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -21,7 +21,7 @@ def make_project_template(project_template_name, project_tasks=None): create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), ] doc = frappe.get_doc( - {"doctype": "Project Template", "name": "project_template_name", "project_type": "Internal"} + {"doctype": "Project Template", "name": project_template_name, "project_type": "Internal"} ) for task in project_tasks: doc.append("tasks", {"task": task.name}) From 64ac6ae8ddcdb63e620fab70be2dbd50813d5189 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:08:11 +0100 Subject: [PATCH 08/11] chore: remove redundant comments --- erpnext/projects/doctype/project/project.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 19ac8ab9a000..fc0047dbc19a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -102,13 +102,8 @@ def validate(self): self.validate_from_to_dates("actual_start_date", "actual_end_date") def copy_from_template_task(self): - """ - Copy tasks from template - """ - # has a template, and no loaded tasks, so lets create if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): template = frappe.get_cached_doc("Project Template", self.project_template) - # create tasks from template project_tasks = [] tmp_task_details = [] for task in template.tasks: @@ -140,13 +135,8 @@ def create_task_from_template(self, task_details): ).insert() def copy_from_template_data(self): - """ - Copy data from template - """ if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): - # has a template, and no loaded tasks, so lets create if not self.expected_start_date: - # project starts today self.expected_start_date = today() template = frappe.get_doc("Project Template", self.project_template) From c50b8846326c6bf9a457f5ebf475beaf96a5a751 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:17:51 +0100 Subject: [PATCH 09/11] refactor: reduce nesting --- erpnext/projects/doctype/project/project.py | 46 +++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index fc0047dbc19a..2d391d2f1fa4 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -102,17 +102,19 @@ def validate(self): self.validate_from_to_dates("actual_start_date", "actual_end_date") def copy_from_template_task(self): - if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): - template = frappe.get_cached_doc("Project Template", self.project_template) - project_tasks = [] - tmp_task_details = [] - for task in template.tasks: - template_task_details = frappe.get_doc("Task", task.task) - tmp_task_details.append(template_task_details) - task = self.create_task_from_template(template_task_details) - project_tasks.append(task) - - self.dependency_mapping(tmp_task_details, project_tasks) + if not self._project_template_applies(): + return + + template = frappe.get_doc("Project Template", self.project_template) + project_tasks = [] + tmp_task_details = [] + for task in template.tasks: + template_task_details = frappe.get_doc("Task", task.task) + tmp_task_details.append(template_task_details) + task = self.create_task_from_template(template_task_details) + project_tasks.append(task) + + self.dependency_mapping(tmp_task_details, project_tasks) def create_task_from_template(self, task_details): return frappe.get_doc( @@ -135,14 +137,24 @@ def create_task_from_template(self, task_details): ).insert() def copy_from_template_data(self): - if self.project_template and not frappe.db.get_all("Task", dict(project=self.name), limit=1): - if not self.expected_start_date: - self.expected_start_date = today() + if not self._project_template_applies(): + return - template = frappe.get_doc("Project Template", self.project_template) + if not self.expected_start_date: + self.expected_start_date = today() - if not self.project_type: - self.project_type = template.project_type + if not self.project_type: + self.project_type = frappe.db.get_value("Project Template", self.project_template, "project_type") + + def _project_template_applies(self) -> bool: + """Return True if the project template should be applied.""" + if not self.project_template: + return False + + if frappe.db.exists("Task", dict(project=self.name)): + return False + + return True def calculate_start_date(self, task_details): self.start_date = add_days(self.expected_start_date, task_details.start) From 1bd56efdb608ef624ff8cb78f9e463a44d54bb17 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:19:20 +0100 Subject: [PATCH 10/11] fix: maintain backward compatibility --- erpnext/projects/doctype/project/project.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2d391d2f1fa4..fe87d0339e57 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -93,14 +93,17 @@ def before_print(self, settings=None): def validate(self): if not self.is_new(): - self.copy_from_template_data() - self.copy_from_template_task() + self.copy_from_template() self.send_welcome_email() self.update_costing() self.update_percent_complete() self.validate_from_to_dates("expected_start_date", "expected_end_date") self.validate_from_to_dates("actual_start_date", "actual_end_date") + def copy_from_template(self): + self.copy_from_template_data() + self.copy_from_template_task() + def copy_from_template_task(self): if not self._project_template_applies(): return From 0eeaef397e863b03e0771911c83bb5df3efdd497 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:21:04 +0100 Subject: [PATCH 11/11] refactor: function naming --- erpnext/projects/doctype/project/project.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index fe87d0339e57..2e9fa5c9115f 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -101,10 +101,10 @@ def validate(self): self.validate_from_to_dates("actual_start_date", "actual_end_date") def copy_from_template(self): - self.copy_from_template_data() - self.copy_from_template_task() + self.copy_data_from_project_template() + self.copy_tasks_from_project_template() - def copy_from_template_task(self): + def copy_tasks_from_project_template(self): if not self._project_template_applies(): return @@ -139,7 +139,7 @@ def create_task_from_template(self, task_details): ) ).insert() - def copy_from_template_data(self): + def copy_data_from_project_template(self): if not self._project_template_applies(): return @@ -218,10 +218,10 @@ def update_project(self): self.db_update() def before_insert(self): - self.copy_from_template_data() + self.copy_data_from_project_template() def after_insert(self): - self.copy_from_template_task() + self.copy_tasks_from_project_template() if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)