From 56813501eaf62bfa9ff2f90aefadb92c2e7fee82 Mon Sep 17 00:00:00 2001 From: "Stefan-Calin Crainiciuc (stcc)" Date: Mon, 21 Nov 2022 12:36:58 +0000 Subject: [PATCH] [FIX] hr_timesheet: correctly propagates project Steps to reproduce: - Create 2 projects: 'Project1' and 'Project2' - Enable sub-tasks - In Project1 create a task, name it '1'. - In task 1 add a subtask, name it '1:1'. - In task 1:1 add another subtask, name it '1:1:1'. - Go to Timesheet app > add lines for each task. - The lines display name are "Project1 / 1", ... - Go back to task 1, edit the task project to Project2 and save. - The subtasks will have their project_id also set to Project2. - Go to Timesheet app. Issue: Tasks 1 and 1:1 will have their display names updated to "Project2 / 1" and "Project2 / 1:1". However, task 1:1:1 is displayed as "Project1 / 1:1:1", meaning it's project_id was never updated. This happens because of the way changed fields propagate to immediate children, but not to grandchildren, which is intended behaviour. Solution: `_compute_project_id` is also called onchange, meaning that we can use it to update account lines with an outdated project value with the new value. opw-3018301 closes odoo/odoo#109352 X-original-commit: 91b42c806ce2029d9ef678d145ced3138e35bec0 Signed-off-by: Xavier Bol (xbo) Signed-off-by: Stefan-Calin Crainiciuc (stcc) --- addons/hr_timesheet/models/hr_timesheet.py | 4 +- addons/hr_timesheet/tests/test_timesheet.py | 57 +++++++++++++++------ addons/sale_timesheet/models/account.py | 4 ++ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index e77b3833098..8bed71abac0 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -95,7 +95,9 @@ class AccountAnalyticLine(models.Model): @api.depends('task_id', 'task_id.project_id') def _compute_project_id(self): - for line in self.filtered(lambda line: not line.project_id): + for line in self: + if not line.task_id.project_id or line.project_id == line.task_id.project_id: + continue line.project_id = line.task_id.project_id @api.depends('project_id') diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py index 807bf8ce8e9..d11ce577b92 100644 --- a/addons/hr_timesheet/tests/test_timesheet.py +++ b/addons/hr_timesheet/tests/test_timesheet.py @@ -4,7 +4,7 @@ from lxml import etree from odoo.fields import Command -from odoo.tests.common import TransactionCase +from odoo.tests.common import TransactionCase, Form from odoo.exceptions import AccessError, UserError, ValidationError @@ -238,36 +238,63 @@ class TestTimesheet(TestCommonTimesheet): def test_transfert_project(self): """ Transfert task with timesheet to another project. """ Timesheet = self.env['account.analytic.line'] + + # create nested subtasks + task_child = self.env['project.task'].create({ + 'name': 'Task Child', + 'parent_id': self.task1.id, + }) + + task_grandchild = self.env['project.task'].create({ + 'name': 'Task Grandchild', + 'parent_id': task_child.id, + }) + # create a second project self.project_customer2 = self.env['project.project'].create({ 'name': 'Project NUMBER DEUX', 'allow_timesheets': True, }) - # employee 1 log some timesheet on task 1 - Timesheet.create({ + # employee 1 log some timesheet on task 1 and its subtasks + Timesheet.create([{ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'my first timesheet', 'unit_amount': 4, - 'user_id': self.user_employee.id, - }) + 'employee_id': self.empl_employee.id, + }, { + 'project_id': self.project_customer.id, + 'task_id': task_child.id, + 'name': 'my second timesheet', + 'unit_amount': 4, + 'employee_id': self.empl_employee.id, + }, { + 'project_id': self.project_customer.id, + 'task_id': task_grandchild.id, + 'name': 'my third timesheet', + 'unit_amount': 4, + 'employee_id': self.empl_employee.id, + }]) timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_customer.id)]) timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_customer2.id)]) - self.assertEqual(timesheet_count1, 1, "One timesheet in project 1") - self.assertEqual(timesheet_count2, 0, "No timesheet in project 2") - self.assertEqual(len(self.task1.timesheet_ids), 1, "The timesheet should be linked to task 1") + self.assertEqual(timesheet_count1, 3, "3 timesheets should be linked to Project1") + self.assertEqual(timesheet_count2, 0, "No timesheets should be linked to Project2") + self.assertEqual(len(self.task1.timesheet_ids), 1, "The timesheet should be linked to task1") + self.assertEqual(len(task_child.timesheet_ids), 1, "The timesheet should be linked to task_child") + self.assertEqual(len(task_grandchild.timesheet_ids), 1, "The timesheet should be linked to task_grandchild") - # change project of task 1 - self.task1.write({ - 'project_id': self.project_customer2.id - }) + # change project of task 1 from form to trigger onchange + with Form(self.task1) as task_form: + task_form.project_id = self.project_customer2 timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_customer.id)]) timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_customer2.id)]) - self.assertEqual(timesheet_count1, 0, "No timesheet in project 1") - self.assertEqual(timesheet_count2, 1, "Still one timesheet in project 2") - self.assertEqual(len(self.task1.timesheet_ids), 1, "The timesheet still should be linked to task 1") + self.assertEqual(timesheet_count1, 0, "There are still timesheets linked to Project1") + self.assertEqual(timesheet_count2, 3, "3 timesheets should be linked to Project2") + self.assertEqual(len(self.task1.timesheet_ids), 1, "The timesheet still should be linked to task1") + self.assertEqual(len(task_child.timesheet_ids), 1, "The timesheet still should be linked to task_child") + self.assertEqual(len(task_grandchild.timesheet_ids), 1, "The timesheet still should be linked to task_grandchild") # it is forbidden to set a task with timesheet without project with self.assertRaises(UserError): diff --git a/addons/sale_timesheet/models/account.py b/addons/sale_timesheet/models/account.py index 7a89aa97eba..48294ee8e1b 100644 --- a/addons/sale_timesheet/models/account.py +++ b/addons/sale_timesheet/models/account.py @@ -75,6 +75,10 @@ class AccountAnalyticLine(models.Model): def _compute_partner_id(self): super(AccountAnalyticLine, self.filtered(lambda t: t._is_not_billed()))._compute_partner_id() + @api.depends('timesheet_invoice_id.state') + def _compute_project_id(self): + super(AccountAnalyticLine, self.filtered(lambda t: t._is_not_billed()))._compute_project_id() + def _is_not_billed(self): self.ensure_one() return not self.timesheet_invoice_id or self.timesheet_invoice_id.state == 'cancel'