From 86f5ca73d945db5e74aa21004ff984a1ca194d87 Mon Sep 17 00:00:00 2001 From: Anjali Date: Mon, 26 Jul 2021 13:42:19 +0000 Subject: [PATCH] [IMP] sale_(project): generic UX improvement for project Purpose of the commit is to improve the generic UX of task and project views. task-2590412 closes odoo/odoo#75509 Related: odoo/enterprise#20423 Signed-off-by: Laurent Stukkens (ltu) --- addons/hr_timesheet/models/project.py | 33 +++++-- addons/hr_timesheet/views/project_views.xml | 16 +++- addons/project/__manifest__.py | 1 + addons/project/models/project.py | 34 ++++++- addons/project/security/ir.model.access.csv | 1 + addons/project/static/src/css/project.css | 1 - addons/project/views/project_views.xml | 93 +++++++++++++++---- addons/project/wizard/__init__.py | 1 + .../project/wizard/project_report_wizard.py | 24 +++++ .../wizard/project_report_wizard_views.xml | 40 ++++++++ addons/sale_project/models/sale_order.py | 4 +- 11 files changed, 220 insertions(+), 28 deletions(-) create mode 100644 addons/project/wizard/project_report_wizard.py create mode 100644 addons/project/wizard/project_report_wizard_views.xml diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index 19dcf45fa7e..c7de0f8482e 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -48,6 +48,7 @@ class Project(models.Model): remaining_hours = fields.Float(compute='_compute_remaining_hours', string='Remaining Invoiced Time', compute_sudo=True) has_planned_hours_tasks = fields.Boolean(compute='_compute_remaining_hours', compute_sudo=True, help="True if any of the project's task has a set planned hours") + is_project_overtime = fields.Boolean('Project in Overtime', compute='_compute_remaining_hours', search='_search_is_project_overtime', compute_sudo=True) def _compute_encode_uom_in_days(self): self.encode_uom_in_days = self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day') @@ -88,13 +89,31 @@ class Project(models.Model): fields=['planned_hours:sum', 'remaining_hours:sum'], groupby='project_id') group_per_project_id = {group['project_id'][0]: group for group in group_read} for project in self: - group = group_per_project_id.get(project.id) - if group: - project.remaining_hours = group.get('remaining_hours') - project.has_planned_hours_tasks = bool(group.get('planned_hours')) - else: - project.remaining_hours = 0 - project.has_planned_hours_tasks = False + group = group_per_project_id.get(project.id, {}) + project.remaining_hours = group.get('remaining_hours', 0) + project.has_planned_hours_tasks = bool(group.get('planned_hours', False)) + project.is_project_overtime = group.get('remaining_hours', 0) < 0 + + @api.model + def _search_is_project_overtime(self, operator, value): + if not isinstance(value, bool): + raise ValueError(_('Invalid value: %s') % value) + if operator not in ['=', '!=']: + raise ValueError(_('Invalid operator: %s') % operator) + + query = """ + SELECT P.id + FROM project_project P + LEFT JOIN project_task T ON P.id = T.project_id + WHERE T.planned_hours IS NOT NULL + GROUP BY P.id + HAVING SUM(T.remaining_hours) < 0 + """ + if (operator == '=' and value is True) or (operator == '!=' and value is False): + operator_new = 'inselect' + else: + operator_new = 'not inselect' + return [('id', operator_new, (query, ()))] @api.constrains('allow_timesheets', 'analytic_account_id') def _check_allow_timesheet(self): diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index 04c0a9538d1..ab2aa90fba3 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -212,7 +212,7 @@ - @@ -340,6 +340,20 @@ + + project.project.view.inherit.timesheet + project.project + + + + + + + + + + + project.task.view.graph.inherited project.task diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index 0c26d807ba3..fc30d6266d4 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -35,6 +35,7 @@ 'views/project_project_stage_views.xml', 'wizard/project_share_wizard_views.xml', 'views/project_collaborator_views.xml', + 'wizard/project_report_wizard_views.xml', 'views/project_views.xml', 'views/res_partner_views.xml', 'views/res_config_settings_views.xml', diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 64d3e1d738d..858b4fbc1e2 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -310,6 +310,7 @@ class Project(models.Model): last_update_color = fields.Integer(compute='_compute_last_update_color') milestone_ids = fields.One2many('project.milestone', 'project_id', copy=True) milestone_count = fields.Integer(compute='_compute_milestone_count') + is_milestone_exceeded = fields.Boolean(compute="_compute_is_milestone_exceeded", search='_search_is_milestone_exceeded') _sql_constraints = [ ('project_date_greater', 'check(date >= date_start)', 'Error! Project start date must be before project end date.') @@ -380,6 +381,37 @@ class Project(models.Model): for project in self: project.milestone_count = mapped_count.get(project.id, 0) + @api.depends('milestone_ids', 'milestone_ids.is_reached', 'milestone_ids.deadline') + def _compute_is_milestone_exceeded(self): + today = fields.Date.context_today(self) + read_group = self.env['project.milestone'].read_group([ + ('project_id', 'in', self.ids), + ('is_reached', '=', False), + ('deadline', '<', today)], ['project_id'], ['project_id']) + mapped_count = {group['project_id'][0]: group['project_id_count'] for group in read_group} + for project in self: + project.is_milestone_exceeded = bool(mapped_count.get(project.id, 0)) + + @api.model + def _search_is_milestone_exceeded(self, operator, value): + if not isinstance(value, bool): + raise ValueError(_('Invalid value: %s') % value) + if operator not in ['=', '!=']: + raise ValueError(_('Invalid operator: %s') % operator) + + query = """ + SELECT P.id + FROM project_project P + LEFT JOIN project_milestone M ON P.id = M.project_id + WHERE M.is_reached IS false + AND M.deadline < CAST(now() AS date) + """ + if (operator == '=' and value is True) or (operator == '!=' and value is False): + operator_new = 'inselect' + else: + operator_new = 'not inselect' + return [('id', operator_new, (query, ()))] + @api.depends('alias_name', 'alias_domain') def _compute_alias_value(self): for project in self: @@ -876,7 +908,7 @@ class Task(models.Model): return stages.search(['|', ('id', 'in', stages.ids), ('user_id', '=', self.env.user.id)]) active = fields.Boolean(default=True) - name = fields.Char(string='Title', tracking=True, required=True, index=True) + name = fields.Char(string='Title', tracking=True, required=True, index=True, translate=True) description = fields.Html(string='Description') priority = fields.Selection([ ('0', 'Normal'), diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv index 99ba6bdd054..9e76ea5002f 100644 --- a/addons/project/security/ir.model.access.csv +++ b/addons/project/security/ir.model.access.csv @@ -41,3 +41,4 @@ access_project_collaborator_user,project.collaborator.user,model_project_collabo access_project_collaborator_portal,project.collaborator.portal,model_project_collaborator,base.group_portal,1,0,0,0 access_project_share_manager,project.share.wizard.manager,model_project_share_wizard,project.group_project_manager,1,1,1,0 access_project_personal_stage,project.personal.stage.user,model_project_task_stage_personal,base.group_user,1,1,1,1 +access_project_report_wizard_manager,project.report.wizard.project.manager,model_project_report_wizard,project.group_project_manager,1,1,1,0 diff --git a/addons/project/static/src/css/project.css b/addons/project/static/src/css/project.css index 5ec2db81ed7..5366b7d8e17 100644 --- a/addons/project/static/src/css/project.css +++ b/addons/project/static/src/css/project.css @@ -8,7 +8,6 @@ * max-width: map-get($container-max-widths, md) - (2 * $o-horizontal-padding); */ max-width: 688px; - margin-right: auto; } .o_form_project_tasks .note-editable { diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 8418c09f8ac..35f0b1b48f6 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -95,6 +95,23 @@ + + Sub-tasks + project.task + tree,kanban,form,calendar,pivot,graph,activity + + [('id', 'child_of', active_id), ('id', '!=', active_id)] + {'show_project_update': False, 'default_parent_id': active_id} + +

+ No tasks found. Let's create one! +

+ To get things done, use activities and status on tasks.
+ Chat in real time or by email to collaborate efficiently. +

+
+
+ project.task.type.search @@ -276,6 +293,14 @@
+
- @@ -854,6 +886,15 @@ in Recurrence
+