[FIX] project: dup milestones on tasks when dup project

Current behaviour:
When duplicating a project, new milestones are copied for the new
project, but none of them are assigned to the copied tasks like in
the original project.

Expected behaviour:
The new tasks in the new project should have the corresponding copy
of the milestone that were assigned in the original project.

Steps to reproduce:
- Install Project
- Duplicate "Office Design" (it has milestones)
- Observe that the tasks in the new project don't have milestones
  assigned to them, like in the original project.

Reason for the problem:
When we copy the tasks, they have the milestones of the original
project correctly assigned to them, but since the project of the
milestone is different from the project of the task (former
references the original project, while the latter references the
copied project), so in `_compute_milestone_id`, the milestone of the
task is set to False.

Fix:
Remove `copy=True` from `milestone_ids` on the project, and copy the
milestone by hand. This allows us to use an overwrite of `copy()`
for `project.milestone`, and we create a mapping between the old
milestones and the new ones in the context, similar to how we did
with `task_mapping`. With this we can assign the newly created
milestones on the copied tasks correctly (while preserving the
mapping like in the original project).

Affected versions:
- 16.0
- saas-16.1
- saas-16.2
- master

opw-3254868

closes odoo/odoo#118487

X-original-commit: aad60a2c8a887f3ea226af99be9b162673c33ec8
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
Signed-off-by: Piryns Victor (pivi) <pivi@odoo.com>
This commit is contained in:
Victor Piryns (pivi)
2023-04-17 14:14:13 +02:00
parent 378954a486
commit cb73921bef
4 changed files with 63 additions and 1 deletions
@@ -108,3 +108,13 @@ class ProjectMilestone(models.Model):
def _get_data_list(self):
return [ms._get_data() for ms in self]
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
if default is None:
default = {}
milestone_copy = super(ProjectMilestone, self).copy(default)
if self.project_id.allow_milestones:
milestone_mapping = self.env.context.get('milestone_mapping', {})
milestone_mapping[self.id] = milestone_copy.id
return milestone_copy
+5 -1
View File
@@ -204,7 +204,7 @@ class Project(models.Model):
('done', 'Done'),
], default='to_define', compute='_compute_last_update_status', store=True, readonly=False, required=True)
last_update_color = fields.Integer(compute='_compute_last_update_color')
milestone_ids = fields.One2many('project.milestone', 'project_id', copy=True)
milestone_ids = fields.One2many('project.milestone', 'project_id')
milestone_count = fields.Integer(compute='_compute_milestone_count', groups='project.group_project_milestone')
milestone_count_reached = fields.Integer(compute='_compute_milestone_reached_count', groups='project.group_project_milestone')
is_milestone_exceeded = fields.Boolean(compute="_compute_is_milestone_exceeded", search='_search_is_milestone_exceeded')
@@ -385,6 +385,10 @@ class Project(models.Model):
project = super(Project, self_with_mail_context).copy(default)
for follower in self.message_follower_ids:
project.message_subscribe(partner_ids=follower.partner_id.ids, subtype_ids=follower.subtype_ids.ids)
if self.allow_milestones:
if 'milestone_mapping' not in self.env.context:
self = self.with_context(milestone_mapping=dict())
project.milestone_ids = [milestone.copy().id for milestone in self.milestone_ids]
if 'tasks' not in default:
self.map_tasks(project.id)
+3
View File
@@ -677,6 +677,9 @@ class Task(models.Model):
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]})
if self.allow_milestones:
milestone_mapping = self.env.context.get('milestone_mapping', {})
task_copy.milestone_id = milestone_mapping.get(task_copy.milestone_id.id, task_copy.milestone_id.id)
return task_copy
@api.model
@@ -49,3 +49,48 @@ class TestProjectMilestone(TestProjectCommon):
self.task_1.project_id = self.project_goats
self.assertFalse(self.task_1.milestone_id, 'No milestone should be linked to the task since its project has changed')
def test_duplicate_project_duplicates_milestones_on_tasks(self):
"""
Test when we duplicate the project with tasks linked to its' milestones,
that the tasks in the new project are also linked to the duplicated milestones of the new project
We can't really robustly test that the mapping of task -> milestone is the same in the old and new project,
the workaround way of testing the mapping is basing ourselves on unique names and check that those are equals in the test.
"""
# original unique_names, used to map between the original -> copy
unique_name_1 = "unique_name_1"
unique_name_2 = "unique_name_2"
unique_names = [unique_name_1, unique_name_2]
project = self.env['project.project'].create({'name': 'Test project'})
milestones = self.env['project.milestone'].create([{
'name': unique_name_1,
'project_id': project.id,
}, {
'name': unique_name_2,
'project_id': project.id,
}])
tasks = self.env['project.task'].create([{
'name': unique_name_1,
'project_id': project.id,
'milestone_id': milestones[0].id,
}, {
'name': unique_name_2,
'project_id': project.id,
'milestone_id': milestones[1].id,
}])
self.assertEqual(tasks[0].milestone_id, milestones[0])
self.assertEqual(tasks[1].milestone_id, milestones[1])
project_copy = project.copy()
self.assertNotEqual(project_copy.milestone_ids, False)
self.assertEqual(project.milestone_ids.mapped('name'), project_copy.milestone_ids.mapped('name'))
self.assertNotEqual(project_copy.task_ids, False)
for milestone in project_copy.task_ids.milestone_id:
self.assertTrue(milestone in project_copy.milestone_ids)
for unique_name in unique_names:
orig_task = project.task_ids.filtered(lambda t: t.name == unique_name)
copied_task = project_copy.task_ids.filtered(lambda t: t.name == unique_name)
self.assertEqual(orig_task.name, copied_task.name, "The copied_task should be a copy of the original task")
self.assertNotEqual(copied_task.milestone_id, False,
"We should copy the milestone and it shouldn't be reset to false from _compute_milestone_id")
self.assertEqual(orig_task.milestone_id.name, copied_task.milestone_id.name,
"the copied milestone should be a copy if the original ")