From ccf6973b394d045a90a8bec64c06439b9dc41305 Mon Sep 17 00:00:00 2001 From: "Pierrot (prro)" <prro@odoo.com> Date: Mon, 18 Oct 2021 15:22:00 +0000 Subject: [PATCH] [FIX] [hr,sale]_timesheet: update timesheet's partner from Task How to reproduce the problem: - Install hr_timesheet and create a project. - Create a task in the project, and assign a Partner as the Customer - In the Timesheet tab of the Task, add a new entry. Save the Task. - Edit the task again and change the Customer. Save again. -> the partner on the Timesheet entry did not change. With sale_timesheet, the Sales Order Item of the timesheet entry can be updated from the Sales Order Item of the Task, but it isn't the case with the partner, which is not logical. Solution : Now, the partner_id is computed and will automatically be updated from the Task's Customer/partner_id. If sale_timesheet is installed, it will first filter out the invoiced timesheet entry: we don't want to change an already invoiced entry. opw-2616784 closes odoo/odoo#78577 Signed-off-by: LTU-Odoo <IT-Ideas@users.noreply.github.com> --- addons/hr_timesheet/models/hr_timesheet.py | 7 ++ addons/hr_timesheet/tests/test_timesheet.py | 22 ++++++ addons/sale_timesheet/models/account.py | 4 ++ .../tests/test_sale_timesheet.py | 72 +++++++++++++++++++ 4 files changed, 105 insertions(+) diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index 8acabeb89163..e537c3dc76b5 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -48,11 +48,18 @@ class AccountAnalyticLine(models.Model): employee_id = fields.Many2one('hr.employee', "Employee", domain=_domain_employee_id) department_id = fields.Many2one('hr.department', "Department", compute='_compute_department_id', store=True, compute_sudo=True) encoding_uom_id = fields.Many2one('uom.uom', compute='_compute_encoding_uom_id') + partner_id = fields.Many2one(compute='_compute_partner_id', store=True, readonly=False) def _compute_encoding_uom_id(self): for analytic_line in self: analytic_line.encoding_uom_id = analytic_line.company_id.timesheet_encode_uom_id + @api.depends('task_id.partner_id', 'project_id.partner_id') + def _compute_partner_id(self): + for timesheet in self: + if timesheet.project_id: + timesheet.partner_id = timesheet.task_id.partner_id or timesheet.project_id.partner_id + @api.depends('task_id', 'task_id.project_id') def _compute_project_id(self): for line in self.filtered(lambda line: not line.project_id): diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py index fdd64970c790..f1386efde57e 100644 --- a/addons/hr_timesheet/tests/test_timesheet.py +++ b/addons/hr_timesheet/tests/test_timesheet.py @@ -3,6 +3,7 @@ from odoo.tests.common import TransactionCase from odoo.exceptions import AccessError, UserError +from odoo.tests import tagged class TestCommonTimesheet(TransactionCase): @@ -83,6 +84,7 @@ class TestCommonTimesheet(TransactionCase): 'user_id': self.user_manager.id, }) +@tagged('-at_install', 'post_install') class TestTimesheet(TestCommonTimesheet): def setUp(self): @@ -284,3 +286,23 @@ class TestTimesheet(TestCommonTimesheet): }, { 'amount': -12.0, }]) + + def test_recompute_partner_from_task_customer_change(self): + partner2 = self.env['res.partner'].create({ + 'name': 'Customer Task 2', + 'email': 'customer2@task.com', + 'phone': '43', + }) + + timesheet_entry = self.env['account.analytic.line'].create({ + 'project_id': self.project_customer.id, + 'task_id': self.task1.id, + 'name': 'my only timesheet', + 'unit_amount': 4, + }) + + self.assertEqual(timesheet_entry.partner_id, self.partner, "The timesheet entry's partner should be equal to the task's partner/customer") + + self.task1.write({'partner_id': partner2}) + + self.assertEqual(timesheet_entry.partner_id, partner2, "The timesheet entry's partner should still be equal to the task's partner/customer, after the change") diff --git a/addons/sale_timesheet/models/account.py b/addons/sale_timesheet/models/account.py index df56dbd22113..f513ca90210b 100644 --- a/addons/sale_timesheet/models/account.py +++ b/addons/sale_timesheet/models/account.py @@ -61,6 +61,10 @@ class AccountAnalyticLine(models.Model): def _compute_so_line(self): for timesheet in self._get_not_billed(): # Get only the timesheets are not yet invoiced timesheet.so_line = timesheet.project_id.allow_billable and timesheet._timesheet_determine_sale_line(timesheet.task_id, timesheet.employee_id, timesheet.project_id) + + @api.depends('timesheet_invoice_id.state') + def _compute_partner_id(self): + super(AccountAnalyticLine, self._get_not_billed())._compute_partner_id() def _get_not_billed(self): return self.filtered(lambda t: not t.timesheet_invoice_id or t.timesheet_invoice_id.state == 'cancel') diff --git a/addons/sale_timesheet/tests/test_sale_timesheet.py b/addons/sale_timesheet/tests/test_sale_timesheet.py index 9e3411e18450..cc8ea6102d28 100644 --- a/addons/sale_timesheet/tests/test_sale_timesheet.py +++ b/addons/sale_timesheet/tests/test_sale_timesheet.py @@ -599,3 +599,75 @@ class TestSaleTimesheet(TestCommonSaleTimesheet): self.assertEqual(timesheet_count1, 1, "One timesheet in project_global") self.assertEqual(timesheet_count2, 1, "Still one timesheet in project_template") self.assertEqual(len(task.timesheet_ids), 2, "The 2 timesheet still should be linked to task") + + def test_change_customer_and_SOL_after_invoiced_timesheet(self): + sale_order1 = self.env['sale.order'].create({ + 'partner_id': self.partner_a.id, + 'partner_invoice_id': self.partner_a.id, + 'partner_shipping_id': self.partner_a.id, + 'pricelist_id': self.company_data['default_pricelist'].id, + }) + sale_order2 = self.env['sale.order'].create({ + 'partner_id': self.partner_b.id, + 'partner_invoice_id': self.partner_b.id, + 'partner_shipping_id': self.partner_b.id, + 'pricelist_id': self.company_data['default_pricelist'].id, + }) + so1_product_global_project_so_line = self.env['sale.order.line'].create({ + 'name': self.product_delivery_timesheet2.name, + 'product_id': self.product_delivery_timesheet2.id, + 'product_uom_qty': 50, + 'product_uom': self.product_delivery_timesheet2.uom_id.id, + 'price_unit': self.product_delivery_timesheet2.list_price, + 'order_id': sale_order1.id, + }) + so2_product_global_project_so_line = self.env['sale.order.line'].create({ + 'name': self.product_delivery_timesheet2.name, + 'product_id': self.product_delivery_timesheet2.id, + 'product_uom_qty': 20, + 'product_uom': self.product_delivery_timesheet2.uom_id.id, + 'price_unit': self.product_delivery_timesheet2.list_price, + 'order_id': sale_order2.id, + }) + + sale_order1.action_confirm() + sale_order2.action_confirm() + + task_so1 = self.env['project.task'].search([('sale_line_id', '=', so1_product_global_project_so_line.id)]) + task_so2 = self.env['project.task'].search([('sale_line_id', '=', so2_product_global_project_so_line.id)]) + + self.assertEqual(self.partner_a, task_so1.partner_id, "The Customer of the first task should be equal to partner_a.") + self.assertEqual(self.partner_b, task_so2.partner_id, "The Customer of the second task should be equal to partner_b.") + self.assertEqual(sale_order1.partner_id, task_so1.partner_id, "The Customer of the first task should be equal to the Customer of the first Sales Order.") + self.assertEqual(sale_order2.partner_id, task_so2.partner_id, "The Customer of the second task should be equal to the Customer of the second Sales Order.") + + task_so1_timesheet1 = self.env['account.analytic.line'].create({ + 'name': 'Test Line1', + 'project_id': task_so1.project_id.id, + 'task_id': task_so1.id, + 'unit_amount': 5, + 'employee_id': self.employee_user.id, + }) + + invoice = sale_order1._create_invoices() + invoice.action_post() + + self.assertEqual(self.partner_a, task_so1_timesheet1.partner_id, "The Task's Timesheet entry should have the same partner than on the task 1 and Sales Order 1.") + + task_so1_timesheet2 = self.env['account.analytic.line'].create({ + 'name': 'Test Line2', + 'project_id': task_so1.project_id.id, + 'task_id': task_so1.id, + 'unit_amount': 3, + 'employee_id': self.employee_user.id, + }) + + task_so1.write({ + 'partner_id': self.partner_b.id, + 'sale_line_id': so2_product_global_project_so_line.id, + }) + + self.assertEqual(self.partner_a, task_so1_timesheet1.partner_id, "The Task's first Timesheet entry should not have changed as it was already invoiced (its partner should still be partner_a).") + self.assertEqual(self.partner_b, task_so1_timesheet2.partner_id, "The Task's second Timesheet entry should have its partner changed, as it was not invoiced and the Task's partner/customer changed.") + self.assertEqual(so1_product_global_project_so_line, task_so1_timesheet1.so_line, "The Task's first Timesheet entry should not have changed as it was already invoiced (its so_line should still be equal to the first Sales Order line).") + self.assertEqual(so2_product_global_project_so_line, task_so1_timesheet2.so_line, "The Task's second Timesheet entry should have it's so_line changed, as the Sales Order Item of the Task changed, and this entry was not invoiced.") -- GitLab