diff --git a/addons/project/models/project.py b/addons/project/models/project.py index c0ed1f12086..cffef538612 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -471,6 +471,8 @@ class Project(models.Model): new_subtasks = self.env['project.task'] # We want to copy archived task, but do not propagate an active_test context key task_ids = self.env['project.task'].with_context(active_test=False).search([('project_id', '=', self.id), ('parent_id', '=', False)]).ids + if self.allow_task_dependencies and 'task_mapping' not in self.env.context: + self = self.with_context(task_mapping=dict()) for task in self.env['project.task'].browse(task_ids): # preserve task name and stage, normally altered during copy defaults = self._map_tasks_default_valeus(task, project) @@ -1084,10 +1086,10 @@ class Task(models.Model): allow_task_dependencies = fields.Boolean(related='project_id.allow_task_dependencies') # Tracking of this field is done in the write function depend_on_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="task_id", - column2="depends_on_id", string="Blocked By", tracking=True, + column2="depends_on_id", string="Blocked By", tracking=True, copy=False, domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]") dependent_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="depends_on_id", - column2="task_id", string="Block", + column2="task_id", string="Block", copy=False, domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]") dependent_tasks_count = fields.Integer(string="Dependent Tasks", compute='_compute_dependent_tasks_count') @@ -1540,6 +1542,8 @@ class Task(models.Model): def copy(self, default=None): if default is None: default = {} + if self.allow_task_dependencies and 'task_mapping' not in self.env.context: + self = self.with_context(task_mapping=dict()) has_default_name = bool(default.get('name', '')) if not has_default_name: default['name'] = _("%s (copy)", self.name) @@ -1547,7 +1551,16 @@ class Task(models.Model): default['recurrence_id'] = self.recurrence_id.copy().id if self.allow_subtasks: default['child_ids'] = [child.copy({'name': child.name} if has_default_name else None).id for child in self.child_ids] - return super(Task, self).copy(default) + task_copy = super(Task, self).copy(default) + if self.allow_task_dependencies: + task_mapping = self.env.context.get('task_mapping') + task_mapping[self.id] = task_copy.id + new_tasks = task_mapping.values() + self.write({'depend_on_ids': [Command.unlink(t.id) for t in self.depend_on_ids if t.id in new_tasks]}) + self.write({'dependent_ids': [Command.unlink(t.id) for t in self.dependent_ids if t.id in new_tasks]}) + task_copy.write({'depend_on_ids': [Command.link(task_mapping.get(t.id, t.id)) for t in self.depend_on_ids]}) + task_copy.write({'dependent_ids': [Command.link(task_mapping.get(t.id, t.id)) for t in self.dependent_ids]}) + return task_copy @api.model def get_empty_list_help(self, help): diff --git a/addons/project/tests/test_task_dependencies.py b/addons/project/tests/test_task_dependencies.py index 984f28bbfbc..e225cc100da 100644 --- a/addons/project/tests/test_task_dependencies.py +++ b/addons/project/tests/test_task_dependencies.py @@ -159,3 +159,74 @@ class TestTaskDependencies(TestProjectCommon): 'name': 'My Ducks Project' }) self.assertFalse(self.project_ducks.allow_task_dependencies, "New Projects allow_task_dependencies should default to group_project_task_dependencies") + + def test_duplicate_project_with_task_dependencies(self): + self.project_pigs.allow_task_dependencies = True + self.task_1.depend_on_ids = self.task_2 + pigs_copy = self.project_pigs.copy() + + task1_copy = pigs_copy.task_ids.filtered(lambda t: t.name == 'Pigs UserTask') + task2_copy = pigs_copy.task_ids.filtered(lambda t: t.name == 'Pigs ManagerTask') + + self.assertEqual(len(task1_copy), 1, "Should only contain 1 copy of UserTask") + self.assertEqual(len(task2_copy), 1, "Should only contain 1 copy of ManagerTask") + + self.assertEqual(task1_copy.depend_on_ids.ids, [task2_copy.id], + "Copy should only create a relation between both copy if they are both part of the project") + + task1_copy.depend_on_ids = self.task_1 + + pigs_copy_copy = pigs_copy.copy() + task1_copy_copy = pigs_copy_copy.task_ids.filtered(lambda t: t.name == 'Pigs UserTask') + + self.assertEqual(task1_copy_copy.depend_on_ids.ids, [self.task_1.id], + "Copy should not alter the relation if the other task is in a different project") + + def test_duplicate_project_with_subtask_dependencies(self): + parent_task = self.env['project.task'].with_context({'mail_create_nolog': True}).create({ + 'name': 'Parent Task', + 'project_id': self.project_goats.id, + 'child_ids': [ + Command.create({'name': 'Node 1'}), + Command.create({'name': 'SuperNode 2', 'child_ids': [Command.create({'name': 'Node 2'})]}), + Command.create({'name': 'Node 3'}), + ], + }) + + node1 = parent_task.child_ids[0] + node2 = parent_task.child_ids[1].child_ids + node3 = parent_task.child_ids[2] + + node1.dependent_ids = node2 + node2.dependent_ids = node3 + + # Test copying the whole Node tree + parent_task_copy = parent_task.copy() + parent_copy_node1 = parent_task_copy.child_ids[0] + parent_copy_node2 = parent_task_copy.child_ids[1].child_ids + parent_copy_node3 = parent_task_copy.child_ids[2] + + # Relation should only be copied between the newly created node + self.assertEqual(len(parent_copy_node1.dependent_ids), 1) + self.assertEqual(parent_copy_node1.dependent_ids.ids, parent_copy_node2.ids, 'Node1copy - Node2copy relation should be present') + self.assertEqual(len(parent_copy_node2.dependent_ids), 1) + self.assertEqual(parent_copy_node2.dependent_ids.ids, parent_copy_node3.ids, 'Node2copy - Node3copy relation should be present') + + # Original Node should not have new relation + self.assertEqual(len(node1.dependent_ids), 1) + self.assertEqual(node1.dependent_ids.ids, node2.ids, 'Only Node1 - Node2 relation should be present') + self.assertEqual(len(node2.dependent_ids), 1) + self.assertEqual(node2.dependent_ids.ids, node3.ids, 'Only Node2 - Node3 relation should be present') + + # Test copying Node inside the chain + single_copy_node2 = node2.copy() + + # Relation should be present between the other original node and the newly copied node + self.assertEqual(len(single_copy_node2.depend_on_ids), 1) + self.assertEqual(single_copy_node2.depend_on_ids.ids, node1.ids, 'Node1 - Node2copy relation should be present') + self.assertEqual(len(single_copy_node2.dependent_ids), 1) + self.assertEqual(single_copy_node2.dependent_ids.ids, node3.ids, 'Node2copy - Node3 relation should be present') + + # Original Node should have new relations + self.assertEqual(len(node1.dependent_ids), 2) + self.assertEqual(len(node3.depend_on_ids), 2)