diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py index c9371d19caa..179d7412c7e 100755 --- a/addons/sale_project/models/project.py +++ b/addons/sale_project/models/project.py @@ -738,11 +738,13 @@ class ProjectTask(models.Model): continue if not task.sale_line_id: # if the project_id is set then it means the task is classic task or a subtask with another project than its parent. + # To determine the sale_line_id, we first need to look at the parent before the project to manage the case of subtasks. + # Two sub-tasks in the same project do not necessarily have the same sale_line_id (need to look at the parent task). sale_line = False - if task.project_id.sale_line_id and task.project_id.partner_id.commercial_partner_id == task.partner_id.commercial_partner_id: - sale_line = task.project_id.sale_line_id - elif task.parent_id.sale_line_id and task.parent_id.partner_id.commercial_partner_id == task.partner_id.commercial_partner_id: + if task.parent_id.sale_line_id and task.parent_id.partner_id.commercial_partner_id == task.partner_id.commercial_partner_id: sale_line = task.parent_id.sale_line_id + elif task.project_id.sale_line_id and task.project_id.partner_id.commercial_partner_id == task.partner_id.commercial_partner_id: + sale_line = task.project_id.sale_line_id task.sale_line_id = sale_line or task.milestone_id.sale_line_id # check sale_line_id and customer are coherent if task.sale_line_id.order_partner_id.commercial_partner_id != task.partner_id.commercial_partner_id: diff --git a/addons/sale_project/tests/test_sale_project.py b/addons/sale_project/tests/test_sale_project.py index 44c7c662bfb..8eec56e2172 100644 --- a/addons/sale_project/tests/test_sale_project.py +++ b/addons/sale_project/tests/test_sale_project.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import Command from odoo.tests.common import new_test_user from .common import TestSaleProjectCommon from odoo.tests.common import tagged @@ -236,6 +237,103 @@ class TestSaleProject(TestSaleProjectCommon): sale_order.with_context({'disable_cancel_warning': True}).action_cancel() self.assertFalse(self.project_global.sale_line_id, "The project should not be linked to the SOL anymore") + def test_links_with_sale_order_line(self): + """ + Check that the subtasks are linked to the correct sale order line. + """ + product_A, product_B, product_C = self.env['product.product'].create([ + { + 'name': 'product_A', + 'lst_price': 100.0, + 'detailed_type': 'service', + 'service_tracking': 'task_in_project', + }, + { + 'name': 'product_B', + 'lst_price': 100.0, + 'detailed_type': 'service', + 'service_tracking': 'task_in_project', + }, + { + 'name': 'product_C', + 'lst_price': 100.0, + 'detailed_type': 'service', + 'service_tracking': 'task_in_project', + }, + ]) + sale_order_first, sale_order_second = self.env['sale.order'].create([ + { + 'partner_id': self.partner.id, + 'order_line': [ + Command.create({'product_id': product_A.id}), + Command.create({'product_id': product_B.id}), + ] + }, + { + 'partner_id': self.partner.id, + 'order_line': [ + Command.create({'product_id': product_C.id}), + ] + } + ]) + (sale_order_first + sale_order_second).action_confirm() + + sale_order_line_A = sale_order_first.order_line.filtered(lambda sol: sol.product_id == product_A) + sale_order_line_B = sale_order_first.order_line - sale_order_line_A + sale_order_line_C = sale_order_second.order_line + + project_first = sale_order_first.project_ids + project_second = sale_order_second.project_ids + + task_A = sale_order_first.tasks_ids.filtered(lambda task: task.sale_line_id == sale_order_line_A) + task_B = sale_order_first.tasks_ids - task_A + task_C = sale_order_second.tasks_ids + + # [CASE 1] Parent in the same project --> use parent's sale order line + task_A.write({ + 'child_ids': [ + Command.create({'name': 'Sub A in first project', 'project_id': project_first.id}), + ] + }) + task_B.write({ + 'child_ids': [ + Command.create({'name': 'Sub B in first project', 'project_id': project_first.id}), + ] + }) + task_C.write({ + 'child_ids': [ + Command.create({'name': 'Sub C in second project', 'project_id': project_second.id}), + ] + }) + self.assertEqual(task_A.child_ids.sale_line_id, sale_order_line_A) + self.assertEqual(task_B.child_ids.sale_line_id, sale_order_line_B) + self.assertEqual(task_C.child_ids.sale_line_id, sale_order_line_C) + + # [CASE 2] Parent in an other project --> use parent's sale_order line + task_B.write({ + 'child_ids': [ + Command.create({'name': 'Sub B in second project', 'project_id': project_second.id}), + ] + }) + sub_B_second = task_B.child_ids.filtered(lambda sub: sub.name == 'Sub B in second project') + self.assertEqual(sub_B_second.sale_line_id, sale_order_line_B) + + # [CASE 3] Without project --> no sale order line defined + task_B.write({ + 'child_ids': [ + Command.create({'name': 'Sub B without project'}), + ] + }) + sub_B_without = task_B.child_ids.filtered(lambda sub: sub.name == 'Sub B without project') + self.assertEqual(len(sub_B_without.sale_line_id), 0) + + # [CASE 4] Without parent --> use sale order line of the project + task_D = self.env['project.task'].create({ + 'name': 'Task D', + 'project_id': project_first.id, + }) + self.assertEqual(task_D.sale_line_id, project_first.sale_line_id) + def test_compute_project_and_task_button(self): """ Ensures that when a sale order has at least one sol with a service product whose service-policy is either delivered on milestone or ordered prepaid, then the