From be49740169f25e86cd2020ce32541d1990dfec6a Mon Sep 17 00:00:00 2001 From: sofiagvaladze Date: Tue, 29 Mar 2022 09:25:13 +0000 Subject: [PATCH] [IMP] hr: make Launch Plan multi-employee action Purpose: Increase usability of the Plan feature This change implies changing 'hr.plan.wizard' m2o employee_id field into m2m employee_ids field. task - 2797331 closes odoo/odoo#88119 Related: odoo/enterprise#26264 Related: odoo/upgrade#3439 Signed-off-by: Kevin Baptiste --- addons/hr/models/hr_plan_activity_type.py | 18 ++-- addons/hr/static/src/js/hr_employee.js | 22 ++++- addons/hr/views/hr_employee_views.xml | 3 + addons/hr/wizard/hr_plan_wizard.py | 103 +++++++++++++++++----- addons/hr/wizard/hr_plan_wizard_views.xml | 8 +- 5 files changed, 121 insertions(+), 33 deletions(-) diff --git a/addons/hr/models/hr_plan_activity_type.py b/addons/hr/models/hr_plan_activity_type.py index ba569e38bb7..cab65ff80f0 100644 --- a/addons/hr/models/hr_plan_activity_type.py +++ b/addons/hr/models/hr_plan_activity_type.py @@ -39,24 +39,28 @@ class HrPlanActivityType(models.Model): plan_type.summary = plan_type.activity_type_id.summary def get_responsible_id(self, employee): + warning = False if self.responsible == 'coach': if not employee.coach_id: - raise UserError(_('Coach of employee %s is not set.', employee.name)) + warning = _('Coach of employee %s is not set.', employee.name) responsible = employee.coach_id.user_id if not responsible: - raise UserError(_('User of coach of employee %s is not set.', employee.name)) + warning = _('User of coach of employee %s is not set.', employee.name) elif self.responsible == 'manager': if not employee.parent_id: - raise UserError(_('Manager of employee %s is not set.', employee.name)) + warning = _('Manager of employee %s is not set.', employee.name) responsible = employee.parent_id.user_id if not responsible: - raise UserError(_("The manager of %s should be linked to a user.", employee.name)) + warning = _("The manager of %s should be linked to a user.", employee.name) elif self.responsible == 'employee': responsible = employee.user_id if not responsible: - raise UserError(_('The employee %s should be linked to a user.', employee.name)) + warning = _('The employee %s should be linked to a user.', employee.name) elif self.responsible == 'other': responsible = self.responsible_id if not responsible: - raise UserError(_('No specific user given on activity %s.', self.activity_type_id.name)) - return responsible + warning = _('No specific user given on activity %s.', self.activity_type_id.name) + return { + 'responsible': responsible, + 'warning': warning, + } diff --git a/addons/hr/static/src/js/hr_employee.js b/addons/hr/static/src/js/hr_employee.js index f22bbe4bad2..0edc4929574 100644 --- a/addons/hr/static/src/js/hr_employee.js +++ b/addons/hr/static/src/js/hr_employee.js @@ -108,8 +108,28 @@ odoo.define('hr.employee_chat', function (require) { } } return actionMenuItems; - } + }, + + /** + * When we run Launch Plan list action, in case if we close the + * wizzard, the originally selected employees stay selected. + * + * @override + */ + async _executeButtonAction(actionData, recordData) { + const prom = new Promise((resolve, reject) => { + this.trigger_up('execute_action', { + action_data: actionData, + env: recordData, + on_closed: () => this.isDestroyed() || actionData.context['action_plan'] ? Promise.resolve() : this.reload(), + on_success: resolve, + on_fail: () => this.update({}, { keepSelection: true, reload: false }).then(reject).guardedCatch(reject) + }); + }); + return this.alive(prom); + }, }); + const EmployeeListView = ListView.extend({ config: _.extend({}, ListView.prototype.config, { diff --git a/addons/hr/views/hr_employee_views.xml b/addons/hr/views/hr_employee_views.xml index 15ed05a2083..cd9b4056e95 100644 --- a/addons/hr/views/hr_employee_views.xml +++ b/addons/hr/views/hr_employee_views.xml @@ -229,6 +229,9 @@ hr.employee +
+
diff --git a/addons/hr/wizard/hr_plan_wizard.py b/addons/hr/wizard/hr_plan_wizard.py index 68e21c9eaa5..ca35bbc1fad 100644 --- a/addons/hr/wizard/hr_plan_wizard.py +++ b/addons/hr/wizard/hr_plan_wizard.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models, _ +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError class HrPlanWizard(models.TransientModel): @@ -9,41 +10,97 @@ class HrPlanWizard(models.TransientModel): _description = 'Plan Wizard' def _default_plan_id(self): - employee = self.env['hr.employee'].browse(self.env.context.get('active_id')) + # We know that all employees belong to the same company + employee = self.env['hr.employee'].browse(self.env.context.get('active_ids')[0] if self.env.context.get('active_ids') else []) return self.env['hr.plan'].search([('company_id', '=', employee.company_id.id)], limit=1) plan_id = fields.Many2one('hr.plan', default=lambda self: self._default_plan_id(), domain="[('company_id', '=', company_id)]") - employee_id = fields.Many2one( - 'hr.employee', string='Employee', required=True, - default=lambda self: self.env.context.get('active_id', None), + employee_ids = fields.Many2many( + 'hr.employee', 'hr_employee_hr_plan_wizard_rel', 'employee_id', 'plan_wizard_id', string='Employee', required=True, + default=lambda self: self.env.context.get('active_ids', []), ) - company_id = fields.Many2one(related='employee_id.company_id') + company_id = fields.Many2one('res.company', 'Company', compute='_compute_company_id', required=True) + warning = fields.Html(compute='_compute_warning') + + @api.constrains('employee_ids') + def _check_employee_companies(self): + for wizard in self: + if len(wizard.employee_ids.mapped('company_id')) > 1: + raise ValidationError(_('The employees should belong to the same company.')) + + @api.depends('employee_ids') + def _compute_company_id(self): + for wizard in self: + wizard.company_id = wizard.employee_ids[0].company_id + + def _get_warnings(self): + self.ensure_one() + warnings = set() + for employee in self.employee_ids: + for activity_type in self.plan_id.plan_activity_type_ids: + warning = activity_type.get_responsible_id(employee)['warning'] + if warning: + warnings.add(warning) + return warnings + + @api.depends('employee_ids', 'plan_id') + def _compute_warning(self): + for wizard in self: + warnings = wizard._get_warnings() + + if warnings: + warning_display = _('The plan %s cannot be launched:
    ', wizard.plan_id.name) + for warning in warnings: + warning_display += '
  • %s
  • ' % warning + warning_display += '
' + else: + warning_display = False + wizard.warning = warning_display def _get_activities_to_schedule(self): return self.plan_id.plan_activity_type_ids def action_launch(self): - for activity_type in self._get_activities_to_schedule(): - responsible = activity_type.get_responsible_id(self.employee_id) + self.ensure_one() + for employee in self.employee_ids: + body = _('The plan %s has been started', self.plan_id.name) + activities = set() + for activity_type in self._get_activities_to_schedule(): + responsible = activity_type.get_responsible_id(employee)['responsible'] + if self.env['hr.employee'].with_user(responsible).check_access_rights('read', raise_exception=False): + date_deadline = self.env['mail.activity']._calculate_date_deadline(activity_type.activity_type_id) + employee.activity_schedule( + activity_type_id=activity_type.activity_type_id.id, + summary=activity_type.summary, + note=activity_type.note, + user_id=responsible.id, + date_deadline=date_deadline + ) + activity = _('%(activity)s, assigned to %(name)s, due on the %(deadline)s', activity=activity_type.summary, name=responsible.name, deadline=date_deadline) + activities.add(activity) - if self.env['hr.employee'].with_user(responsible).check_access_rights('read', raise_exception=False): - date_deadline = self.env['mail.activity']._calculate_date_deadline(activity_type.activity_type_id) - self.employee_id.activity_schedule( - activity_type_id=activity_type.activity_type_id.id, - summary=activity_type.summary, - note=activity_type.note, - user_id=responsible.id, - date_deadline=date_deadline - ) + if activities: + body += '
    ' + for activity in activities: + body += '
  • %s
  • ' % activity + body += '
' + employee.message_post(body=body) - for plan in self: - plan.employee_id.message_post(body=_('The plan %s has been started', plan.plan_id.name)) + if len(self.employee_ids) == 1: + return { + 'type': 'ir.actions.act_window', + 'res_model': 'hr.employee', + 'res_id': self.employee_ids.id, + 'name': self.employee_ids.display_name, + 'view_mode': 'form', + 'views': [(False, "form")], + } return { 'type': 'ir.actions.act_window', 'res_model': 'hr.employee', - 'res_id': self.employee_id.id, - 'name': self.employee_id.display_name, - 'view_mode': 'form', - 'views': [(False, "form")], + 'name': _('Launch Plans'), + 'view_mode': 'tree,form', + 'target': 'current', + 'domain': [('id', 'in', self.employee_ids.ids)], } diff --git a/addons/hr/wizard/hr_plan_wizard_views.xml b/addons/hr/wizard/hr_plan_wizard_views.xml index d9da9ced4ec..b9d9690ad69 100644 --- a/addons/hr/wizard/hr_plan_wizard_views.xml +++ b/addons/hr/wizard/hr_plan_wizard_views.xml @@ -9,12 +9,16 @@ - + +