From 70d7f31d589f766f20f8109aaf410fd67e91ddd0 Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Wed, 17 Feb 2021 09:26:14 +0100 Subject: [PATCH 1/5] [IMP] project: add task dependencies feature in project settings This commit brings a new feature for the Project App and project configuration. This feature is called "Task Dependencies" and it allows to the user to determine the order in which to perform tasks in a project. task-2387984 closes #66740 --- addons/project/models/project.py | 1 + addons/project/models/res_config_settings.py | 40 +++++++++++++------ addons/project/security/project_security.xml | 5 +++ addons/project/views/project_views.xml | 11 +++++ .../views/res_config_settings_views.xml | 11 +++++ 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index d3097673d9b..ff2bb37a754 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -219,6 +219,7 @@ class Project(models.Model): help="Project in which sub-tasks of the current project will be created. It can be the current project itself.") allow_subtasks = fields.Boolean('Sub-tasks', default=lambda self: self.env.user.has_group('project.group_subtask_project')) allow_recurring_tasks = fields.Boolean('Recurring Tasks', default=lambda self: self.env.user.has_group('project.group_project_recurring_tasks')) + allow_task_dependencies = fields.Boolean('Task Dependencies', default=lambda self: self.env.user.has_group('project.group_project_task_dependencies')) # rating fields rating_request_deadline = fields.Datetime(compute='_compute_rating_request_deadline', store=True) diff --git a/addons/project/models/res_config_settings.py b/addons/project/models/res_config_settings.py index ae12e7efb23..13d4837002a 100644 --- a/addons/project/models/res_config_settings.py +++ b/addons/project/models/res_config_settings.py @@ -12,22 +12,36 @@ class ResConfigSettings(models.TransientModel): group_subtask_project = fields.Boolean("Sub-tasks", implied_group="project.group_subtask_project") group_project_rating = fields.Boolean("Customer Ratings", implied_group='project.group_project_rating') group_project_recurring_tasks = fields.Boolean("Recurring Tasks", implied_group="project.group_project_recurring_tasks") + group_project_task_dependencies = fields.Boolean("Task Dependencies", implied_group="project.group_project_task_dependencies") + + @api.model + def _get_basic_project_domain(self): + return [] def set_values(self): - # Ensure that settings on existing projects match the above fields projects = self.env["project.project"].search([]) - features = ( - # Pairs of associated (config_flag, project_flag) - ("group_subtask_project", "allow_subtasks"), - ("group_project_rating", "rating_active"), - ("group_project_recurring_tasks", "allow_recurring_tasks"), - ) - for (config_flag, project_flag) in features: - config_flag_global = "project." + config_flag - config_feature_enabled = self[config_flag] - if (self.user_has_groups(config_flag_global) - is not config_feature_enabled): - projects[project_flag] = config_feature_enabled + global_features = { # key: config_flag, value: project_flag + "group_project_rating": "rating_active", + "group_project_recurring_tasks": "allow_recurring_tasks", + } + basic_project_features = { + "group_subtask_project": "allow_subtasks", + "group_project_task_dependencies": "allow_task_dependencies", + } + config_feature_vals = {config_flag: self[config_flag] + for config_flag in global_features.keys() | basic_project_features.keys()} + + def update_projects(projects, features): + for (config_flag, project_flag) in features.items(): + config_flag_global = "project." + config_flag + config_feature_enabled = config_feature_vals[config_flag] + if self.user_has_groups(config_flag_global) != config_feature_enabled: + projects[project_flag] = config_feature_enabled + + # update for all projects + update_projects(projects, global_features) + # update for basic projects + update_projects(projects.filtered_domain(self._get_basic_project_domain()), basic_project_features) super(ResConfigSettings, self).set_values() diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml index befca629ed6..0ad10c19e48 100644 --- a/addons/project/security/project_security.xml +++ b/addons/project/security/project_security.xml @@ -34,6 +34,11 @@ + + Use Task Dependencies + + + diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 6a910f0e154..e91d50ddd45 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -385,6 +385,17 @@ +
+
+ +
+
+
+
diff --git a/addons/project/views/res_config_settings_views.xml b/addons/project/views/res_config_settings_views.xml index 2e2164e0f43..d7191c9e9c6 100644 --- a/addons/project/views/res_config_settings_views.xml +++ b/addons/project/views/res_config_settings_views.xml @@ -59,6 +59,17 @@ +
+
+ +
+
+
+

Time Management

From d2dadbcc3b9e1400654cf37962f155dc57c727d5 Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Thu, 18 Feb 2021 13:35:34 +0100 Subject: [PATCH 2/5] [IMP] project: add task dependencies feature in project.task model This commit adds two new fields in the project.task model. The first one called 'allow_task_dependencies' is a related field to the one project.project model. The other field called 'depend_on_ids' is a Many2many field will contain the tasks which the current task depend on. In the form view of project.task model, these fields are added. task-2387984 closes #66740 --- addons/hr_timesheet/views/project_views.xml | 9 ++++ addons/project/models/project.py | 16 ++++++- .../project/static/src/scss/project_form.scss | 3 ++ addons/project/tests/__init__.py | 1 + .../project/tests/test_task_dependencies.py | 42 +++++++++++++++++++ addons/project/views/project_assets.xml | 1 + addons/project/views/project_views.xml | 18 ++++++++ 7 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 addons/project/static/src/scss/project_form.scss create mode 100644 addons/project/tests/test_task_dependencies.py diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index 9d2fc6a4845..a54f429a15c 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -196,6 +196,15 @@ + + + + + + + + + diff --git a/addons/project/models/project.py b/addons/project/models/project.py index ff2bb37a754..66ccaabbf23 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -650,6 +650,10 @@ class Task(models.Model): # customer portal: include comment and incoming emails in communication history website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])]) + # Task Dependencies fields + allow_task_dependencies = fields.Boolean(related='project_id.allow_task_dependencies') + depend_on_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="task_id", column2="depends_on_id", string="Blocked By") + # recurrence fields allow_recurring_tasks = fields.Boolean(related='project_id.allow_recurring_tasks') recurring_task = fields.Boolean(string="Recurrent") @@ -1172,7 +1176,7 @@ class Task(models.Model): if task.project_id.partner_id: task.partner_id = task.project_id.partner_id else: - task.partner_id = task.project_id.partner_id or task.parent_id.partner_id + task.partner_id = task.project_id.partner_id or task.parent_id.partner_id @api.depends('partner_id.email', 'parent_id.email_from') def _compute_email_from(self): @@ -1393,6 +1397,16 @@ class Task(models.Model): # Actions # ------------ + def action_open_task_form(self): + """ Opens the form view of the task """ + return { + 'view_mode': 'form', + 'res_model': 'project.task', + 'res_id': self.id, + 'type': 'ir.actions.act_window', + 'context': dict(self._context, create=False) + } + def action_recurring_tasks(self): return { 'name': 'Tasks in Recurrence', diff --git a/addons/project/static/src/scss/project_form.scss b/addons/project/static/src/scss/project_form.scss new file mode 100644 index 00000000000..b81797fde42 --- /dev/null +++ b/addons/project/static/src/scss/project_form.scss @@ -0,0 +1,3 @@ +.o_form_project_tasks div.o_field_many2many[name=depend_on_ids] tr.o_selected_row td.o_list_button { + background-color: $table-border-color !important; +} diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py index d5c89ac7b9d..0be5e1c075d 100644 --- a/addons/project/tests/__init__.py +++ b/addons/project/tests/__init__.py @@ -7,3 +7,4 @@ from . import test_project_flow from . import test_project_recurrence from . import test_project_ui from . import test_multicompany +from . import test_task_dependencies diff --git a/addons/project/tests/test_task_dependencies.py b/addons/project/tests/test_task_dependencies.py new file mode 100644 index 00000000000..5650780d3fc --- /dev/null +++ b/addons/project/tests/test_task_dependencies.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from odoo.fields import Command +from odoo.exceptions import ValidationError +from odoo.tests import tagged + +from odoo.addons.project.tests.test_project_base import TestProjectCommon + + +@tagged('-at_install', 'post_install', 'task_dependencies') +class TestTaskDependencies(TestProjectCommon): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.project_pigs.write({ + 'allow_task_dependencies': True, + }) + cls.task_3 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({ + 'name': 'Pigs UserTask 2', + 'user_id': cls.user_projectuser.id, + 'project_id': cls.project_pigs.id, + }) + + def test_task_dependencies(self): + """ Test the task dependencies feature + + Test Case: + ========= + 1) Add task2 as dependency in task1 + 2) Checks if the task1 has the task in depend_on_ids field. + """ + self.assertEqual(len(self.task_1.depend_on_ids), 0, "The task 1 should not have any dependency.") + self.task_1.write({ + 'depend_on_ids': [Command.link(self.task_2.id)], + }) + self.assertEqual(len(self.task_1.depend_on_ids), 1, "The task 1 should have a dependency.") + self.task_1.write({ + 'depend_on_ids': [Command.link(self.task_3.id)], + }) + self.assertEqual(len(self.task_1.depend_on_ids), 2, "The task 1 should have two dependencies.") diff --git a/addons/project/views/project_assets.xml b/addons/project/views/project_assets.xml index 76c047170b5..fdec23a8dfd 100644 --- a/addons/project/views/project_assets.xml +++ b/addons/project/views/project_assets.xml @@ -11,6 +11,7 @@ + diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index e91d50ddd45..97d81f72a88 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -658,6 +658,7 @@ +
- -
-
-
- -
-
-