From e96fc7d3feb95db4583a318312b35483f9309c6a Mon Sep 17 00:00:00 2001 From: luve-odoo Date: Fri, 19 May 2023 14:26:33 +0200 Subject: [PATCH] [IMP] project : gantt view improvements Before this commit, the group by in the gantt view of project didn't allow the user to find records for which there were not scheduled tasks. For example, if a Sale Order didn't have a scheduled task, when grouping by Sale Order, the latter was not displayed. And searching for its name was not displaying it either. After this commit, when a user is searching for a sale order, even if the latter does not have any scheduled task, it is displayed in order to facilitate the scheduling of new tasks. In order to do so, a group expand on sale_order_id for the project.task model have been introduced. closes odoo/odoo#121819 Taskid: 3251630 Related: odoo/enterprise#41257 Signed-off-by: Xavier Bol (xbo) --- addons/project/models/project_task.py | 34 +++++++++++++++++++++++++++ addons/resource/models/utils.py | 2 +- addons/resource/tests/test_utils.py | 4 ++-- addons/sale_project/models/project.py | 14 ++++++++++- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/addons/project/models/project_task.py b/addons/project/models/project_task.py index 657a4158dca..f23d8661e5b 100644 --- a/addons/project/models/project_task.py +++ b/addons/project/models/project_task.py @@ -12,6 +12,7 @@ from odoo.addons.web_editor.controllers.main import handle_history_divergence from odoo.exceptions import UserError, ValidationError, AccessError from odoo.osv import expression from odoo.tools.misc import get_lang +from odoo.addons.resource.models.utils import filter_domain_leaf PROJECT_TASK_READABLE_FIELDS = { @@ -1122,6 +1123,39 @@ class Task(models.Model): return {'date_end': fields.Datetime.now()} return {'date_end': False} + def _search_on_comodel(self, domain, field, comodel, order=None, additional_domain=None): + + def _change_operator(domain): + new_domain = [] + for dom in domain: + if len(dom) == 3: + _, op, value = dom + op = "ilike" if op == "child_of" else op + if isinstance(value, list) and all(isinstance(val, int) for val in value): + new_domain.append(("id", op, value)) + if isinstance(value, str) or (isinstance(value, list) and not all(isinstance(val, str) for val in value)): + new_domain.append(("name", op, value)) + if isinstance(value, int): + new_domain.append(("id", op, [value])) + else: + new_domain.append(dom) + return new_domain + + filtered_domain = filter_domain_leaf(domain, lambda field_to_check: field_to_check in [ + field, + f"{field}.id", + f"{field}.name", + ], { + field: "name", + f"{field}.id": "id", + f"{field}.name": "name", + }) + if not filtered_domain: + return False + if additional_domain: + filtered_domain = expression.AND([filtered_domain, additional_domain]) + return self.env[comodel].search(_change_operator(filtered_domain), order=order) + # --------------------------------------------------- # Subtasks # --------------------------------------------------- diff --git a/addons/resource/models/utils.py b/addons/resource/models/utils.py index fe435f6395d..8f643fea389 100644 --- a/addons/resource/models/utils.py +++ b/addons/resource/models/utils.py @@ -84,8 +84,8 @@ def filter_domain_leaf(domain, field_check, field_name_mapping=None): next_elem = domain.pop() # Browsing the domain backward simplifies the filtering if is_leaf(next_elem): field_name, op, value = next_elem - field_name = field_name_mapping.get(field_name, field_name) if field_check(field_name): + field_name = field_name_mapping.get(field_name, field_name) stack.append((field_name, op, value)) ignored_elems.append(False) else: diff --git a/addons/resource/tests/test_utils.py b/addons/resource/tests/test_utils.py index 2c5db580d6c..e262061d0e7 100644 --- a/addons/resource/tests/test_utils.py +++ b/addons/resource/tests/test_utils.py @@ -50,10 +50,10 @@ class TestExpression(TransactionCase): # Testing field mapping 1 self.assertEqual( - ['&', '!', ('field3', '=', False), ('field3', '!=', 'test')], + [('field4', '!=', 'test')], normalize_domain(utils.filter_domain_leaf( ['|', ('field1', 'in', [1, 2]), '!', ('field2', '=', False), ('field3', '!=', 'test')], lambda field: field == 'field3', - field_name_mapping={'field2': 'field3'}, + field_name_mapping={'field3': 'field4'}, )) ) diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py index 665dc07ddbf..90ffd9ed39d 100755 --- a/addons/sale_project/models/project.py +++ b/addons/sale_project/models/project.py @@ -9,6 +9,7 @@ from odoo import api, fields, models, _, _lt from odoo.exceptions import ValidationError, AccessError from odoo.osv import expression from odoo.tools import Query + from functools import reduce from odoo.addons.project.models.project_task import CLOSED_STATES @@ -752,7 +753,7 @@ class Project(models.Model): class ProjectTask(models.Model): _inherit = "project.task" - sale_order_id = fields.Many2one('sale.order', 'Sales Order', compute='_compute_sale_order_id', store=True, help="Sales order to which the task is linked.") + sale_order_id = fields.Many2one('sale.order', 'Sales Order', compute='_compute_sale_order_id', store=True, help="Sales order to which the task is linked.", group_expand="_group_expand_sales_order") sale_line_id = fields.Many2one( 'sale.order.line', 'Sales Order Item', copy=True, tracking=True, index='btree_not_null', recursive=True, @@ -776,6 +777,17 @@ class ProjectTask(models.Model): def SELF_READABLE_FIELDS(self): return super().SELF_READABLE_FIELDS | {'allow_billable', 'sale_order_id', 'sale_line_id', 'display_sale_order_button'} + @api.model + def _group_expand_sales_order(self, sales_orders, domain, order): + start_date = self._context.get('gantt_start_date') + scale = self._context.get('gantt_scale') + if not (start_date and scale): + return sales_orders + search_on_comodel = self._search_on_comodel(domain, "sale_order_id", "sale.order", order) + if search_on_comodel: + return search_on_comodel + return sales_orders + @api.depends('sale_line_id', 'project_id', 'partner_id.commercial_partner_id', 'allow_billable') def _compute_sale_order_id(self): for task in self: