From 0f3281e3b1dc943b81d79d2a8f82fca7b95b8186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 29 Aug 2018 15:37:18 +0200 Subject: [PATCH] [REF] hr_holidays: clean leave and allocation access rights Purpose - clean access rights; - solve some access and security issues; Access specifications - an user - can see all leaves; - cannot see name field of leaves belonging to other user as it may contain private information that we don't want to share to other people than HR people; - can modify only its own not validated leaves (except writing on state to avoid bypassing validation); - can discuss on its leave requests; - an Officer - can see all leaves; - can validate "HR" single validation leaves from people if - he is the employee manager; - he is the department manager; - he is member of the same department; - target employee has no manager and no department manager; - can validate "Manager" single validation leaves from people if - he is the employee manager; - he is the department manager; - target employee has no manager and no department manager; - can first validate "Both" double validation leaves from people like "HR" single validation, moving the leaves to validate1 state; - cannot validate its own leaves; - a Manager - can do everything he wants In top of that, multi company rule are correctly added. In reporting model using in "summary" name field should also be stripped if you are not an HR officer and if it is not your own request. Concerning allocation requests: implement same rules except constraint on name field as it is less private and therefore can be seen. This commit is linked to task ID 1876795 and PR #26656. --- addons/hr_holidays/models/hr_leave.py | 168 +++++--- .../hr_holidays/models/hr_leave_allocation.py | 117 +++--- addons/hr_holidays/report/hr_leave_report.py | 20 +- .../security/hr_holidays_security.xml | 66 ++- addons/hr_holidays/tests/__init__.py | 1 + addons/hr_holidays/tests/common.py | 30 +- .../hr_holidays/tests/test_access_rights.py | 392 ++++++++++++++++++ .../hr_holidays/tests/test_holidays_flow.py | 48 +-- 8 files changed, 650 insertions(+), 192 deletions(-) create mode 100644 addons/hr_holidays/tests/test_access_rights.py diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index b112c02b2df..35286c1fc9d 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -8,7 +8,7 @@ import math from datetime import timedelta from odoo import api, fields, models -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools import float_compare from odoo.tools.translate import _ @@ -16,27 +16,47 @@ from odoo.addons.resource.models.resource import HOURS_PER_DAY _logger = logging.getLogger(__name__) -class HolidaysRequest(models.Model): - """ - Here are the rights associated with the flow of leave requests - State Groups Restriction - ============================================================================================================ - Draft Anybody Own Request only and state in [confirm, refuse] - Holiday Manager State in [confirm, refuse] - Confirm All State = draft - Validate1 Holiday Officer Not his own Request and state = confirm - Holiday Manager Not his own Request or has no manager and State = confirm - Validate Holiday Officer Not his own Request and state = confirm - Holiday Manager Not his own Request or has no manager and State in [validate1, confirm] - Refuse Holiday Officer State in [confirm, validate, validate1] - Holiday Manager State in [confirm, validate, validate1] - ============================================================================================================ +class HolidaysRequest(models.Model): + """ Leave Requests Access specifications + + - a regular employee / user + - can see all leaves; + - cannot see name field of leaves belonging to other user as it may contain + private information that we don't want to share to other people than + HR people; + - can modify only its own not validated leaves (except writing on state to + bypass approval); + - can discuss on its leave requests; + - can reset only its own leaves; + - cannot validate any leaves; + - an Officer + - can see all leaves; + - can validate "HR" single validation leaves from people if + - he is the employee manager; + - he is the department manager; + - he is member of the same department; + - target employee has no manager and no department manager; + - can validate "Manager" single validation leaves from people if + - he is the employee manager; + - he is the department manager; + - target employee has no manager and no department manager; + - can first validate "Both" double validation leaves from people like "HR" + single validation, moving the leaves to validate1 state; + - cannot validate its own leaves; + - can reset only its own leaves; + - can refuse all leaves; + - a Manager + - can do everything he wants + + On top of that multicompany rules apply based on company defined on the + leave request leave type. """ _name = "hr.leave" _description = "Leave" _order = "date_from desc" _inherit = ['mail.thread', 'mail.activity.mixin'] + _mail_post_access = 'read' def _default_domain_holiday_status(self): if self.user_has_groups('hr_holidays.group_hr_holidays_manager'): @@ -56,7 +76,6 @@ class HolidaysRequest(models.Model): def _default_employee(self): return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) - name = fields.Char('Description') state = fields.Selection([ ('draft', 'To Submit'), @@ -126,25 +145,28 @@ class HolidaysRequest(models.Model): holiday.number_of_days = -holiday.number_of_days_temp @api.multi + @api.depends('state', 'employee_id', 'department_id') def _compute_can_reset(self): - """ User can reset a leave request if it is its own leave request - or if he is an Hr Manager. - """ - user = self.env.user - group_hr_manager = self.env.ref('hr_holidays.group_hr_holidays_manager') for holiday in self: - if group_hr_manager in user.groups_id or holiday.employee_id and holiday.employee_id.user_id == user: + try: + holiday._check_approval_update('draft') + except (AccessError, UserError): + holiday.can_reset = False + else: holiday.can_reset = True - @api.depends('employee_id', 'department_id') + @api.depends('state', 'employee_id', 'department_id') def _compute_can_approve(self): - """ User can only approve a leave request if it is not his own - Exception : User is holiday manager and has no manager - """ for holiday in self: - # User is holiday manager and has no manager - manager = self.user_has_groups('hr_holidays.group_hr_holidays_manager') - holiday.can_approve = (holiday.employee_id.user_id.id != self.env.uid) or manager + try: + if holiday.state == 'confirm' and holiday.holiday_status_id.validation_type == 'both': + holiday._check_approval_update('validate1') + else: + holiday._check_approval_update('validate') + except (AccessError, UserError): + holiday.can_approve = False + else: + holiday.can_approve = True @api.onchange('holiday_type') def _onchange_type(self): @@ -264,8 +286,9 @@ class HolidaysRequest(models.Model): dto = fields.Datetime.from_string(leave.date_to) if dfrom and dto and (dfrom < vstart or dto > vstop): - raise UserError(_('You can take %s only between %s and %s') % (leave.holiday_status_id.display_name, \ - leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop)) + raise UserError( + _('You can take %s only between %s and %s') % ( + leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop)) @api.model def create(self, values): @@ -280,9 +303,29 @@ class HolidaysRequest(models.Model): holiday.activity_update() return holiday + def _read_from_database(self, field_names, inherited_field_names=[]): + if 'name' in field_names and 'employee_id' not in field_names: + field_names.append('employee_id') + super(HolidaysRequest, self)._read_from_database(field_names, inherited_field_names) + if 'name' in field_names: + if self.user_has_groups('hr_holidays.group_hr_holidays_user'): + return + current_employee = self.env['hr.employee'].sudo().search([('user_id', '=', self.env.uid)], limit=1) + for record in self: + emp_id = record._cache.get('employee_id', [False])[0] + if emp_id != current_employee.id: + try: + record._cache['name'] + record._cache['name'] = '*****' + except Exception: + # skip SpecialValue (e.g. for missing record or access right) + pass + @api.multi def write(self, values): employee_id = values.get('employee_id', False) + if values.get('state'): + self._check_approval_update(values['state']) result = super(HolidaysRequest, self).write(values) self.add_follower(employee_id) if 'employee_id' in values: @@ -373,8 +416,6 @@ class HolidaysRequest(models.Model): @api.multi def action_draft(self): for holiday in self: - if not holiday.can_reset: - raise UserError(_('Only an HR Manager or the concerned employee can reset to draft.')) if holiday.state not in ['confirm', 'refuse']: raise UserError(_('Leave request state must be "Refused" or "To Approve" in order to reset to Draft.')) holiday.write({ @@ -401,22 +442,10 @@ class HolidaysRequest(models.Model): def action_approve(self): # if validation_type == 'both': this method is the first approval approval # if validation_type != 'both': this method calls action_validate() below - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) - - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) if any(holiday.state != 'confirm' for holiday in self): raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.')) - for holiday in self: - validation_type = holiday.holiday_status_id.validation_type - manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id - if (validation_type in ['hr', 'both']) and (manager and manager != current_employee)\ - and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('You must be %s manager to approve this leave') % (holiday.employee_id.name)) - elif validation_type == 'manager' and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('You must be a Human Resource Manager to approve this Leave')) - + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id}) self.filtered(lambda hol: not hol.validation_type == 'both').action_validate() self.activity_update() @@ -424,18 +453,10 @@ class HolidaysRequest(models.Model): @api.multi def action_validate(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) - - if any(not holiday.can_approve for holiday in self): - raise UserError(_('Only your manager can approve your leave requests')) - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) for holiday in self: if holiday.state not in ['confirm', 'validate1']: raise UserError(_('Leave request must be confirmed in order to approve it.')) - if holiday.state == 'validate1' and not holiday.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('Only an HR Manager can apply the second approval on leave requests.')) holiday.write({'state': 'validate'}) if holiday.validation_type == 'both': @@ -459,9 +480,6 @@ class HolidaysRequest(models.Model): @api.multi def action_refuse(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can refuse leave requests.')) - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) for holiday in self: if holiday.state not in ['confirm', 'validate', 'validate1']: @@ -480,6 +498,40 @@ class HolidaysRequest(models.Model): self.activity_update() return True + def _check_approval_update(self, state): + """ Check if target state is achievable. """ + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') + is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager') + for holiday in self: + val_type = holiday.holiday_status_id.validation_type + if state == 'confirm': + continue + + if state == 'draft': + if holiday.employee_id != current_employee and not is_manager: + raise UserError(_('Only a Leave Manager can reset other people leaves.')) + continue + + if not is_officer: + raise UserError(_('Only a Leave Officer or Manager can approve or refuse leave requests.')) + + if is_officer: + # use ir.rule based first access check: department, members, ... (see security.xml) + holiday.check_access_rule('write') + + if holiday.employee_id == current_employee and not is_manager: + raise UserError(_('Only a Leave Manager can approve its own requests.')) + + if (state == 'validate1' and val_type == 'both') or (state == 'validate' and val_type == 'manager'): + manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id + if (manager and manager != current_employee) and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): + raise UserError(_('You must be either %s\'s manager or Leave manager to approve this leave') % (holiday.employee_id.name)) + + if state == 'validate' and val_type == 'both': + if not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): + raise UserError(_('Only an Leave Manager can apply the second approval on leave requests.')) + # ------------------------------------------------------------ # Activity methods # ------------------------------------------------------------ diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index 17cfddd53a9..7c6f848d710 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -6,32 +6,18 @@ import logging from odoo import api, fields, models -from odoo.exceptions import UserError +from odoo.exceptions import AccessError, UserError from odoo.tools.translate import _ _logger = logging.getLogger(__name__) class HolidaysAllocation(models.Model): - """ - Here are the rights associated with the flow of allocation requests - - State Groups Restriction - ============================================================================================================ - Draft Anybody Own Allocation only and state in [confirm, refuse] - Holiday Manager State in [confirm, refuse] - Confirm All State = draft - Validate1 Holiday Officer Not his own Allocation and state = confirm - Holiday Manager Not his own Allocation or has no manager and State = confirm - Validate Holiday Officer Not his own Allocation and state = confirm - Holiday Manager Not his own Allocation or has no manager and State in [validate1, confirm] - Refuse Holiday Officer State in [confirm, validate, validate1] - Holiday Manager State in [confirm, validate, validate1] - ============================================================================================================ - """ + """ Allocation Requests Access specifications: similar to leave requests """ _name = "hr.leave.allocation" _description = "Leaves Allocation" _inherit = ['mail.thread', 'mail.activity.mixin'] + _mail_post_access = 'read' def _default_domain_holiday_status_id(self): if self.user_has_groups('hr_holidays.group_hr_holidays_manager'): @@ -103,25 +89,28 @@ class HolidaysAllocation(models.Model): holiday.number_of_days = holiday.number_of_days_temp @api.multi + @api.depends('state', 'employee_id', 'department_id') def _compute_can_reset(self): - """ User can reset a leave request if it is its own leave request - or if he is an Hr Manager. - """ - user = self.env.user - group_hr_manager = self.env.ref('hr_holidays.group_hr_holidays_manager') - for holiday in self: - if group_hr_manager in user.groups_id or holiday.employee_id and holiday.employee_id.user_id == user: - holiday.can_reset = True + for allocation in self: + try: + allocation._check_approval_update('draft') + except (AccessError, UserError): + allocation.can_reset = False + else: + allocation.can_reset = True - @api.depends('employee_id', 'department_id') + @api.depends('state', 'employee_id', 'department_id') def _compute_can_approve(self): - """ User can only approve a leave request if it is not his own - Exception : User is holiday manager and has no manager - """ - for holiday in self: - # User is holiday manager and has no manager - manager = self.user_has_groups('hr_holidays.group_hr_holidays_manager') - holiday.can_approve = (holiday.employee_id.user_id.id != self.env.uid) or manager + for allocation in self: + try: + if allocation.state == 'confirm' and allocation.holiday_status_id.validation_type == 'both': + allocation._check_approval_update('validate1') + else: + allocation._check_approval_update('validate') + except (AccessError, UserError): + allocation.can_approve = False + else: + allocation.can_approve = True @api.onchange('holiday_type') def _onchange_type(self): @@ -185,6 +174,8 @@ class HolidaysAllocation(models.Model): @api.multi def write(self, values): employee_id = values.get('employee_id', False) + if values.get('state'): + self._check_approval_update(values['state']) result = super(HolidaysAllocation, self).write(values) self.add_follower(employee_id) return result @@ -220,8 +211,6 @@ class HolidaysAllocation(models.Model): @api.multi def action_draft(self): for holiday in self: - if not holiday.can_reset: - raise UserError(_('Only an HR Manager or the concerned employee can reset to draft.')) if holiday.state not in ['confirm', 'refuse']: raise UserError(_('Leave request state must be "Refused" or "To Approve" in order to reset to Draft.')) holiday.write({ @@ -248,21 +237,10 @@ class HolidaysAllocation(models.Model): def action_approve(self): # if validation_type == 'both': this method is the first approval approval # if validation_type != 'both': this method calls action_validate() below - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) - - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) if any(holiday.state != 'confirm' for holiday in self): raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.')) - for holiday in self: - validation_type = holiday.holiday_status_id.validation_type - manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id - if (validation_type in ['hr', 'both']) and (manager and manager != current_employee)\ - and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('You must be %s manager to approve this leave') % (holiday.employee_id.name)) - elif validation_type == 'manager' and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('You must be a Human Resource Manager to approve this Leave')) + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id}) self.filtered(lambda hol: not hol.validation_type == 'both').action_validate() @@ -271,18 +249,10 @@ class HolidaysAllocation(models.Model): @api.multi def action_validate(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) - - if any(not holiday.can_approve for holiday in self): - raise UserError(_('Only your manager can approve your allocations')) - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) for holiday in self: if holiday.state not in ['confirm', 'validate1']: raise UserError(_('Leave request must be confirmed in order to approve it.')) - if holiday.state == 'validate1' and not holiday.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('Only an HR Manager can apply the second approval on leave requests.')) holiday.write({'state': 'validate'}) if holiday.validation_type == 'both': @@ -304,9 +274,6 @@ class HolidaysAllocation(models.Model): @api.multi def action_refuse(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can refuse leave requests.')) - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) for holiday in self: if holiday.state not in ['confirm', 'validate', 'validate1']: @@ -321,6 +288,40 @@ class HolidaysAllocation(models.Model): self.activity_update() return True + def _check_approval_update(self, state): + """ Check if target state is achievable. """ + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') + is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager') + for holiday in self: + val_type = holiday.holiday_status_id.validation_type + if state == 'confirm': + continue + + if state == 'draft': + if holiday.employee_id != current_employee and not is_manager: + raise UserError(_('Only a Leave Manager can reset other people leaves.')) + continue + + if not is_officer: + raise UserError(_('Only a Leave Officer or Manager can approve or refuse leave requests.')) + + if is_officer: + # use ir.rule based first access check: department, members, ... (see security.xml) + holiday.check_access_rule('write') + + if holiday.employee_id == current_employee and not is_manager: + raise UserError(_('Only a Leave Manager can approve its own requests.')) + + if (state == 'validate1' and val_type == 'both') or (state == 'validate' and val_type == 'manager'): + manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id + if (manager and manager != current_employee) and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): + raise UserError(_('You must be either %s\'s manager or Leave manager to approve this leave') % (holiday.employee_id.name)) + + if state == 'validate' and val_type == 'both': + if not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'): + raise UserError(_('Only an Leave Manager can apply the second approval on leave requests.')) + # ------------------------------------------------------------ # Activity methods # ------------------------------------------------------------ diff --git a/addons/hr_holidays/report/hr_leave_report.py b/addons/hr_holidays/report/hr_leave_report.py index 3e918920c52..50f4c1a69ca 100644 --- a/addons/hr_holidays/report/hr_leave_report.py +++ b/addons/hr_holidays/report/hr_leave_report.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models, tools +from odoo import api, fields, models, tools class LeaveReport(models.Model): @@ -78,3 +78,21 @@ class LeaveReport(models.Model): from hr_leave as request) leaves ); """) + + def _read_from_database(self, field_names, inherited_field_names=[]): + if 'name' in field_names and 'employee_id' not in field_names: + field_names.append('employee_id') + super(LeaveReport, self)._read_from_database(field_names, inherited_field_names) + if 'name' in field_names: + if self.user_has_groups('hr_holidays.group_hr_holidays_user'): + return + current_employee = self.env['hr.employee'].sudo().search([('user_id', '=', self.env.uid)], limit=1) + for record in self: + emp_id = record._cache.get('employee_id', [False])[0] + if emp_id != current_employee.id: + try: + record._cache['name'] + record._cache['name'] = '*****' + except Exception: + # skip SpecialValue (e.g. for missing record or access right) + pass diff --git a/addons/hr_holidays/security/hr_holidays_security.xml b/addons/hr_holidays/security/hr_holidays_security.xml index 5b5c4eeb8da..7e5f51aa67b 100644 --- a/addons/hr_holidays/security/hr_holidays_security.xml +++ b/addons/hr_holidays/security/hr_holidays_security.xml @@ -18,64 +18,96 @@ - Employee Leaves + Leaves: employee: read all - [('employee_id.user_id','=',user.id)] + [(1, '=', 1)] - - Employee Leaves Create, Write, Unlink + + Leaves: employee: create/write/unlink not validated own [('employee_id.user_id','=',user.id), ('state', 'in', ['draft', 'confirm', 'cancel', 'refuse'])] - - Leaves Officer + + Leaves: officer: read all - ['|','|','|', ('employee_id.user_id', '=', user.id), ('department_id', '=', False), ('department_id.manager_id', '=', False), ('department_id.manager_id.user_id', '=', user.id)] - + [(1, '=', 1)] + + + + + + + + Leaves: officer: create/write/unlink own + managed employee + without department + same or managed department + + ['|', +'|', ('employee_id.user_id', '=', user.id), ('employee_id.parent_id.user_id', '=', user.id), +'|', '|', ('department_id', '=', False), ('department_id.member_ids.user_id', 'in', [user.id]), ('department_id.manager_id.user_id', '=', user.id)] + + - Manager Leaves + Leaves: manager: no limit [(1, '=', 1)] + + Leaves: multi company global rule + + + ['|', ('holiday_status_id.company_id', '=', False), ('holiday_status_id.company_id', 'in', [user.company_id.id])] + + - Employee Allocations + Allocations: employee: read own - [('employee_id.user_id','=',user.id)] + [('employee_id.user_id', '=', user.id)] - - Employee Allocations Create, Write, Unlink + + Allocations: employee: create/write/unlink not validated own [('employee_id.user_id','=',user.id), ('state', 'in', ['draft', 'confirm', 'cancel', 'refuse'])] - - Leaves Officer + + Allocations: officer: read all - ['|','|','|', ('employee_id.user_id', '=', user.id), ('department_id', '=', False), ('department_id.manager_id', '=', False), ('department_id.manager_id.user_id', '=', user.id)] + [(1, '=', 1)] + + + + + + + + Allocations: officer: create/write/unlink own + managed employee + without department + same or managed department + + ['|', +'|', ('employee_id.user_id', '=', user.id), ('employee_id.parent_id.user_id', '=', user.id), +'|', '|', ('department_id', '=', False), ('department_id.member_ids.user_id', 'in', [user.id]), ('department_id.manager_id.user_id', '=', user.id)] - Manager Allocations + Allocations: manager: no limit [(1, '=', 1)] diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py index 1300f22a091..a98044890a5 100644 --- a/addons/hr_holidays/tests/__init__.py +++ b/addons/hr_holidays/tests/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import test_access_rights from . import test_holidays_flow from . import test_hr_leave_type diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py index 277d5c446c5..467eff2037c 100644 --- a/addons/hr_holidays/tests/common.py +++ b/addons/hr_holidays/tests/common.py @@ -15,24 +15,27 @@ class TestHrHolidaysBase(common.TransactionCase): group_employee_id = self.ref('base.group_user') # Test users to use through the various tests - self.user_hruser_id = Users.create({ + self.user_hruser = Users.create({ 'name': 'Armande HrUser', 'login': 'Armande', 'email': 'armande.hruser@example.com', 'groups_id': [(6, 0, [group_employee_id, self.ref('hr_holidays.group_hr_holidays_user')])] - }).id - self.user_hrmanager_id = Users.create({ + }) + self.user_hruser_id = self.user_hruser.id + self.user_hrmanager = Users.create({ 'name': 'Bastien HrManager', 'login': 'bastien', 'email': 'bastien.hrmanager@example.com', 'groups_id': [(6, 0, [group_employee_id, self.ref('hr_holidays.group_hr_holidays_manager')])] }).id - self.user_employee_id = Users.create({ + self.user_hrmanager_id = self.user_hrmanager + self.user_employee = Users.create({ 'name': 'David Employee', 'login': 'david', 'email': 'david.employee@example.com', 'groups_id': [(6, 0, [group_employee_id])] - }).id + }) + self.user_employee_id = self.user_employee.id self.user_hrmanager_2_id = Users.create({ 'name': 'Florence HrManager', 'login': 'florence', @@ -50,21 +53,26 @@ class TestHrHolidaysBase(common.TransactionCase): 'name': 'Research and devlopment', }) - self.employee_emp_id = self.env['hr.employee'].create({ + self.employee_emp = self.env['hr.employee'].create({ 'name': 'David Employee', 'user_id': self.user_employee_id, 'department_id': self.rd_dept.id, - }).id - self.employee_hruser_id = self.env['hr.employee'].create({ + }) + self.employee_emp_id = self.employee_emp.id + + self.employee_hruser = self.env['hr.employee'].create({ 'name': 'Armande HrUser', 'user_id': self.user_hruser_id, 'department_id': self.rd_dept.id, - }).id - self.employee_hrmanager_id = self.env['hr.employee'].create({ + }) + self.employee_hruser_id = self.employee_hruser.id + + self.employee_hrmanager = self.env['hr.employee'].create({ 'name': 'Bastien HrManager', 'user_id': self.user_hrmanager_id, 'department_id': self.hr_dept.id, - }).id + }) + self.employee_hrmanager_id = self.employee_hrmanager.id self.employee_hrmanager_2_id = self.env['hr.employee'].create({ 'name': 'Florence HrManager', diff --git a/addons/hr_holidays/tests/test_access_rights.py b/addons/hr_holidays/tests/test_access_rights.py new file mode 100644 index 00000000000..0631fa10006 --- /dev/null +++ b/addons/hr_holidays/tests/test_access_rights.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import datetime +from dateutil.relativedelta import relativedelta + +from odoo import tests +from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase +from odoo.exceptions import AccessError, UserError +from odoo.tools import mute_logger + + +@tests.tagged('access_rights') +class TestLeavesRights(TestHrHolidaysBase): + + def setUp(self): + super(TestLeavesRights, self).setUp() + self.leave_type = self.env['hr.leave.type'].create({ + 'name': 'Unlimited', + 'limit': True, + 'validation_type': 'hr', + }) + self.rd_dept.manager_id = False + self.hr_dept.manager_id = False + self.employee_emp.parent_id = False + self.employee_leave = self.env['hr.leave'].sudo(self.user_employee_id).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_emp.department_id.id, + 'employee_id': self.employee_emp.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_messaging_by_user(self): + """ User may communicate on its own leaves, even if validated """ + self.employee_leave.sudo(self.user_employee_id).message_post( + body='I haz messaging', + subtype='mail.mt_comment', + message_type='comment' + ) + + self.employee_leave.sudo(self.user_hrmanager_id).action_approve() + + self.employee_leave.sudo(self.user_employee_id).message_post( + body='I still haz messaging', + subtype='mail.mt_comment', + message_type='comment' + ) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_read_by_user_other(self): + """ Users should be able to read other people requests except name field """ + other_leave = self.env['hr.leave'].sudo(self.user_hruser).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_hruser.department_id.id, + 'employee_id': self.employee_hruser.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + res = other_leave.sudo(self.user_employee_id).read(['number_of_days_temp', 'state', 'name']) + self.assertEqual( + res[0]['name'], '*****', + 'Private information should have been stripped, received %s instead' % res[0]['name'] + ) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_read_by_user_other_browse(self): + """ Users should be able to browse other people requests except name field """ + other_leave = self.env['hr.leave'].sudo(self.user_hruser).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_hruser.department_id.id, + 'employee_id': self.employee_hruser.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + self.assertEqual( + other_leave.sudo(self.user_employee_id).name, '*****', + 'Private information should have been stripped, received %s instead' % other_leave.sudo(self.user_employee_id).name + ) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_read_by_user_own(self): + """ Users should be able to read name field of own requests """ + res = self.employee_leave.read(['name', 'number_of_days_temp', 'state']) + self.assertEqual(res[0]['name'], 'Test') + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_read_by_user_own_browse(self): + """ Users should be able to browse name field of own requests """ + self.assertEqual(self.employee_leave.state, 'confirm') + self.assertEqual(self.employee_leave.name, 'Test') + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_update_hr_by_user(self): + """ User may update its leave """ + self.employee_leave.sudo(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'}) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_update_hr_by_user_other(self): + """ User cannot update other people leaves """ + other_leave = self.env['hr.leave'].sudo(self.user_hruser).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_hruser.department_id.id, + 'employee_id': self.employee_hruser.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + with self.assertRaises(AccessError): + other_leave.sudo(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'}) + + # ---------------------------------------- + # Reset + # ---------------------------------------- + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_reset_by_manager(self): + """ Manager resets its own leaves """ + manager_leave = self.env['hr.leave'].sudo(self.user_hrmanager).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_hrmanager.department_id.id, + 'employee_id': self.employee_hrmanager.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + manager_leave.sudo(self.user_hrmanager).action_draft() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_reset_by_manager_other(self): + """ Manager may not reset other leaves """ + self.employee_leave.sudo(self.user_hrmanager).action_draft() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_reset_by_officer(self): + """ Officer resets its own leaves """ + officer_leave = self.env['hr.leave'].sudo(self.user_hruser).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_hruser.department_id.id, + 'employee_id': self.employee_hruser.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + officer_leave.sudo(self.user_hruser).action_draft() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_reset_by_officer_other(self): + """ Officer may not reset other leaves """ + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_hruser).action_draft() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_reset_by_user(self): + """ User resets its own leaves """ + self.employee_leave.sudo(self.user_employee_id).action_draft() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_reset_by_user_other(self): + """ User may not reset other leaves """ + other_leave = self.env['hr.leave'].sudo(self.user_hruser).create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_hruser.department_id.id, + 'employee_id': self.employee_hruser.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + with self.assertRaises(UserError): + other_leave.sudo(self.user_employee_id).action_draft() + + # ---------------------------------------- + # Validation: one validation, HR + # ---------------------------------------- + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_manager(self): + """ Manager validates hr-only leaves """ + self.employee_leave.sudo(self.user_hrmanager_id).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_officer_department(self): + """ Officer validates hr-only leaves for co-workers """ + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_officer_no_department(self): + """ Officer validates hr-only leaves for workers from no department and with no manager """ + self.employee_hruser.write({'department_id': False}) + # TDE FIXME: not sure for this one + # self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_officer_other_department_with_manager(self): + """ Officer may not validate hr-only leaves for workers from another department that has a manager """ + self.employee_hruser.write({'department_id': self.hr_dept.id}) + with self.assertRaises(AccessError): + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_officer_other_department_wo_manager(self): + """ Officer may not validate hr-only leaves for workers from another department that has no manager """ + self.employee_hruser.write({'department_id': self.hr_dept.id}) + with self.assertRaises(AccessError): + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_officer_other_department_manager(self): + """ Officer validates hr-only leaves for workers from another department that he manages """ + self.employee_hruser.write({'department_id': self.hr_dept.id}) + self.employee_leave.sudo().department_id.write({'manager_id': self.employee_hruser.id}) + + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_hr_by_user(self): + """ User may not validate any leaves """ + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_employee_id).action_approve() + + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_employee_id).write({'state': 'validate'}) + + # ---------------------------------------- + # Validation: one validation, manager + # ---------------------------------------- + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_manager_by_manager(self): + """ Manager validates manager-only leaves """ + self.leave_type.write({'validation_type': 'manager'}) + self.employee_leave.sudo(self.user_hrmanager_id).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_manager_by_officer_department_manager(self): + """ Officer validates manager-only leaves for co-workers from department he manages""" + self.leave_type.write({'validation_type': 'manager'}) + self.employee_hruser.write({'department_id': self.hr_dept.id}) + self.employee_leave.sudo().department_id.write({'manager_id': self.employee_hruser.id}) + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_manager_by_officer_department_manager_other(self): + """ Officer may not validates manager-only leaves for co-workers from department he does not""" + self.leave_type.write({'validation_type': 'manager'}) + self.employee_hruser.write({'department_id': self.hr_dept.id}) + self.employee_leave.sudo().department_id.write({'manager_id': self.employee_hrmanager.id}) + with self.assertRaises(AccessError): + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_manager_by_officer_manager(self): + """ Officer validates manager-only leaves for co-workers that he manages""" + self.leave_type.write({'validation_type': 'manager'}) + self.employee_emp.write({'parent_id': self.employee_hruser.id}) + self.employee_leave.sudo(self.user_hruser).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_manager_by_officer_manager_other(self): + """ Officer may not validate manager-only leaves for co-workers he does not manage""" + self.leave_type.write({'validation_type': 'manager'}) + self.employee_emp.write({'parent_id': self.employee_hrmanager.id}) + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_hruser).action_approve() + + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_hruser).write({'state': 'validate'}) + + # ---------------------------------------- + # Validation: double + # ---------------------------------------- + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_both_by_manager(self): + """ Manager validates double validation leaves """ + self.leave_type.write({'validation_type': 'both'}) + self.employee_leave.sudo(self.user_hrmanager_id).action_approve() + self.employee_leave.sudo(self.user_hrmanager_id).action_validate() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_both_by_officer(self): + """ Officer may not validate double validation leaves """ + self.leave_type.write({'validation_type': 'both'}) + self.employee_leave.sudo(self.user_hruser).action_approve() + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_hruser).action_validate() + + with self.assertRaises(UserError): + self.employee_leave.sudo(self.user_hruser).write({'state': 'validate'}) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_both_by_officer_and_manager(self): + """ Officer + Manager complete double validation leaves """ + self.leave_type.write({'validation_type': 'both'}) + self.employee_leave.sudo(self.user_hruser).action_approve() + self.employee_leave.sudo(self.user_hrmanager_id).action_validate() + + +class TestMultiCompany(TestHrHolidaysBase): + + def setUp(self): + super(TestMultiCompany, self).setUp() + self.new_company = self.env['res.company'].create({ + 'name': 'Crocodile Dundee Company', + }) + self.leave_type = self.env['hr.leave.type'].create({ + 'name': 'Unlimited - Company New', + 'limit': True, + 'company_id': self.new_company.id, + 'validation_type': 'hr', + }) + self.rd_dept.manager_id = False + self.hr_dept.manager_id = False + + self.employee_leave = self.env['hr.leave'].create({ + 'name': 'Test', + 'holiday_status_id': self.leave_type.id, + 'department_id': self.employee_emp.department_id.id, + 'employee_id': self.employee_emp.id, + 'date_from': datetime.now(), + 'date_to': datetime.now() + relativedelta(days=1), + 'number_of_days_temp': 1, + }) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_access_other_company_user(self): + employee_leave = self.employee_leave.sudo(self.user_employee) + + with self.assertRaises(AccessError): + name = employee_leave.name + + with self.assertRaises(AccessError): + employee_leave.action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_access_other_company_officer(self): + employee_leave_hruser = self.employee_leave.sudo(self.user_hruser) + + with self.assertRaises(AccessError): + name = employee_leave_hruser.name + + with self.assertRaises(AccessError): + employee_leave_hruser.action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_access_other_company_manager(self): + employee_leave_hrmanager = self.employee_leave.sudo(self.user_hrmanager) + + with self.assertRaises(AccessError): + name = employee_leave_hrmanager.name + + with self.assertRaises(AccessError): + employee_leave_hrmanager.action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_access_no_company_user(self): + self.leave_type.write({'company_id': False}) + employee_leave = self.employee_leave.sudo(self.user_employee) + + name = employee_leave.name + with self.assertRaises(UserError): + employee_leave.action_approve() + self.assertEqual(employee_leave.state, 'confirm') + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_access_no_company_officer(self): + self.leave_type.write({'company_id': False}) + employee_leave_hruser = self.employee_leave.sudo(self.user_hruser) + + name = employee_leave_hruser.name + employee_leave_hruser.action_approve() + self.assertEqual(employee_leave_hruser.state, 'validate') + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_access_no_company_manager(self): + self.leave_type.write({'company_id': False}) + employee_leave_hrmanager = self.employee_leave.sudo(self.user_hrmanager) + + name = employee_leave_hrmanager.name + employee_leave_hrmanager.action_approve() + self.assertEqual(employee_leave_hrmanager.state, 'validate') diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py index 6bbb81d57c0..d48045d2613 100644 --- a/addons/hr_holidays/tests/test_holidays_flow.py +++ b/addons/hr_holidays/tests/test_holidays_flow.py @@ -72,15 +72,6 @@ class TestHolidaysFlow(TestHrHolidaysBase): # Employee creates a leave request for another employee -> should crash HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id) - with self.assertRaises(ValidationError): - HolidaysEmployeeGroup.create({ - 'name': 'Hol10', - 'employee_id': self.employee_hruser_id, - 'holiday_status_id': self.holidays_status_1.id, - 'date_from': (datetime.today() - relativedelta(days=1)), - 'date_to': datetime.today(), - 'number_of_days_temp': 1, - }) Requests.search([('name', '=', 'Hol10')]).unlink() # Employee creates a leave request in a no-limit category hr manager only @@ -97,8 +88,6 @@ class TestHolidaysFlow(TestHrHolidaysBase): self.assertEqual(hol1_user_group.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state') # Employee validates its leave request -> should not work - with self.assertRaises(UserError): - hol1_employee_group.action_approve() self.assertEqual(hol1_manager_group.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request') # HrUser validates the employee leave request -> should work @@ -119,14 +108,8 @@ class TestHolidaysFlow(TestHrHolidaysBase): self.assertEqual(hol12_user_group.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state') # Employee validates its leave request -> should not work - with self.assertRaises(UserError): - hol12_employee_group.action_approve() self.assertEqual(hol12_user_group.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request') - # HrUser validates the employee leave request -> should not work - with self.assertRaises(UserError): - hol12_user_group.action_approve() - # HrManager validate the employee leave request hol12_manager_group.action_approve() self.assertEqual(hol1_user_group.state, 'validate', 'hr_holidays: validates leave request should be in validate state') @@ -169,9 +152,7 @@ class TestHolidaysFlow(TestHrHolidaysBase): }) # HrUser validates the first step aloc1_user_group.action_approve() - # HrUser validates the second step -> should crash - with self.assertRaises(UserError): - aloc1_user_group.action_validate() + # HrManager validates the second step aloc1_user_group.sudo(self.user_hrmanager_id).action_validate() # Checks Employee has effectively some days left @@ -197,9 +178,6 @@ class TestHolidaysFlow(TestHrHolidaysBase): self.assertEqual(hol2.state, 'validate1', 'hr_holidays: first validation should lead to validate1 state') - # HrUser validates the second step -> should crash - with self.assertRaises(UserError): - hol2_user_group.action_validate() # HrManager validates the second step hol2_user_group.sudo(self.user_hrmanager_id).action_validate() self.assertEqual(hol2.state, 'validate', @@ -214,9 +192,6 @@ class TestHolidaysFlow(TestHrHolidaysBase): # Check left days: 2 days left again _check_holidays_status(hol_status_2_employee_group, 2.0, 0.0, 2.0, 2.0) - # Annoyed, HrUser tries to fix its error and tries to reset the leave request -> does not work, only HrManager - with self.assertRaises(UserError): - hol2_user_group.action_draft() self.assertEqual(hol2.state, 'refuse', 'hr_holidays: hr_user should not be able to reset a refused leave request') @@ -302,31 +277,10 @@ class TestHolidaysFlow(TestHrHolidaysBase): 'number_of_days_temp': 1, }) - # But not someone else's leave - with self.assertRaises(AccessError): - hol42.sudo(self.user_employee_id).action_draft() - - # An officer should not be able to reset someone else's leave - with self.assertRaises(UserError): - hol42.sudo(self.user_hruser_id).action_draft() - # A manager should be able to reset someone else's leave hol42.action_draft() hol42.unlink() - # Officer should not be able to approve it's own leave - hol50 = HolidaysEmployeeGroup.sudo(self.user_hruser_id).create({ - 'name': 'Hol50', - 'employee_id': self.employee_hruser_id, - 'holiday_status_id': self.holidays_status_1.id, - 'date_from': (datetime.today() + relativedelta(days=15)).strftime('%Y-%m-%d %H:%M'), - 'date_to': (datetime.today() + relativedelta(days=16)), - 'number_of_days_temp': 1, - }) - - with self.assertRaises(UserError): - hol50.action_approve() - # Manager should be able to approve it's own leave hol51 = HolidaysEmployeeGroup.sudo(self.user_hrmanager_2_id).create({ 'name': 'Hol51',