[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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 ")
|
||||
|
||||
Reference in New Issue
Block a user