From e0b16b22e8a1e6cdf55a8656042db5ae356de15d Mon Sep 17 00:00:00 2001 From: RomainLibert Date: Thu, 14 Mar 2019 17:10:33 +0000 Subject: [PATCH] [IMP] hr_holidays: Improve general UX (back2basics) Purpose ======= Having a clean policy in leave access right. If there are some internal need or bugs, read this before decided if it is an expected brhavior or not. It is what we want in a standrard point of vew. Specification ============= Access rights Policy -------------------- Remove Time Off - Team Leader access right Leave_manager_id is now requried, by default it is = parent_id for admin it is admin by default (data employee_admin) 3 access rights: - Internal User - Time Off - All Approver - Time Off - Administrator 3 fields for "manager" - parent_id - leave_manager_id - manager_id on department Department Is just there for information. So never use it in default filters Rules Policy ------------ Don't forget to take the leave type configuration into account * no validation means automatic * officer validation means you need to be at least holidays_user to approve * manager validation means anyone who is at least leave_manager_id can approve Internal User ------------- In double validation mode he: - can only do the first approval - can see the everyone's leaves with a anonymisation of the leave description - can create a leave (even if leave type is directly approved) - can refuse its own leaves (till not reported in payslip) - can reset to draft his own leaves and reconfirm them - can delete a leave in draft state - can cancel a leave if the date_start is in the future - cannot validate its own leaves If leave type is configured in manager mode, he: - can approve or refuse the leaves if he is leave_manager_id If leave type is configured in both mode, he: - can only do the first approval or refuse for the leave if he is leave_manager_id Time Off - All Approver ----------------------- In double validation mode he: - can only do the second approval - can see, write, read all leaves and perform the second approval. - Can set a leaves as reported in payslip. - Cannot validate its own leaves - cannot configure leave type - cannot create leaves in batch Time Off - Administrator ------------------------ In double validation mode he: - can do all the approvals - can bypass all leaves (approve or refuse). - can configure Time Off Types - can create batch leaves - can validate its own leaves Menu ---- - My Time Off (access rights: internal user) - Dashboard - Time Off Requests - Allocation Requests - My Team (rename into "Everyone", access rights: internal user. default filters on current year and group by employee; default view: gantt can switch to list and form) - Managers - To Approve (internal user who are leave_manager_id see and can approve. See only leave he has to approve (domain)) - Time Off - Allocation - All - Time Off - Allocation - Payroll - Time Off to report. - Reporting (access right: time off administrator) - Time Off Analysis - Report by Department - Configuration (access right: time off administrator) Usability --------- - In all list of "manager menus", add actions to change status in mass - In leave type data: - move Home Working from data to demo data - There are 2 Paid time off, get rid of the company on it and share it on all companies (keep only the one in data) - Leave type like this: - Overtime Compensation/compensatory days (keep only one of both, to avoid having 2 same leaves in demo data). Validation by: team leader and hr officer, no validity date - Paid Time Off 2019. Validation by Team Leader and Payroll Officer. Remove validity, remove 2019. - Unpaid. Can be taken in hours. Approved by Payroll officer and team leader. No allocation needed. - New leave request: order of leave type in the m2o: 1. leaves where allocation are fixed by rh and remaining > 0 and allocated > 0 2. leaves where free allocation. Where reaming is > 0 and allocated >0 3. One already taken 4. All other leaves. - Remove the sequence widget in leave type - The employee should get a notification when his leave is refused "Your "leave_type_name_" planned on "start_date" has been refused" - from the dashboard calendar, the reset to draft should lead to edit (avoid user has to click on edit) - An employee should be notified when a leave is approved "Your [leave_type_name] on [start date] has been approved" Leave Dashboard V2 ------------------ https://drive.google.com/file/d/1pMCqDlecqM7ngvmhJJdWtkG2_GiXHyIX/view?usp=sharing Testing ------- Everything concerning the leave requests has to be tested. All the access rights have been reviewed and need testing. Migration --------- Don't forget to keep the filters in the calendar view (otherwise RIP perfomances and usability) TaskID: 1950998 closes odoo/odoo#33813 Signed-off-by: Yannick Tivisse (yti) --- addons/hr_holidays/__manifest__.py | 1 + addons/hr_holidays/data/hr_holidays_data.xml | 21 +- addons/hr_holidays/data/hr_holidays_demo.xml | 203 +---- addons/hr_holidays/models/hr_employee.py | 21 +- addons/hr_holidays/models/hr_leave.py | 122 +-- addons/hr_holidays/models/hr_leave_type.py | 35 +- .../security/hr_holidays_security.xml | 83 +- .../static/src/js/time_off_calendar.js | 127 +-- .../hr_holidays/static/src/scss/time_off.scss | 23 + .../static/src/xml/time_off_calendar.xml | 36 +- addons/hr_holidays/tests/common.py | 13 +- .../hr_holidays/tests/test_access_rights.py | 765 ++++++++++++------ .../tests/test_change_department.py | 3 - .../hr_holidays/tests/test_holidays_flow.py | 1 + .../hr_holidays/views/hr_holidays_views.xml | 151 ++++ .../views/hr_leave_allocation_views.xml | 28 +- .../hr_holidays/views/hr_leave_template.xml | 1 + .../hr_holidays/views/hr_leave_type_views.xml | 16 - addons/hr_holidays/views/hr_leave_views.xml | 165 ++-- .../hr_holidays/views/mail_activity_views.xml | 6 +- .../hr_holidays_summary_department_views.xml | 6 - 21 files changed, 996 insertions(+), 831 deletions(-) create mode 100644 addons/hr_holidays/static/src/scss/time_off.scss create mode 100644 addons/hr_holidays/views/hr_holidays_views.xml diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py index cc325726348..17e469332be 100644 --- a/addons/hr_holidays/__manifest__.py +++ b/addons/hr_holidays/__manifest__.py @@ -48,6 +48,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p 'views/hr_views.xml', 'views/hr_leave_template.xml', + 'views/hr_holidays_views.xml', ], 'demo': [ 'data/hr_holidays_demo.xml', diff --git a/addons/hr_holidays/data/hr_holidays_data.xml b/addons/hr_holidays/data/hr_holidays_data.xml index 6b7ec7f2a6c..4f4bcb79c35 100644 --- a/addons/hr_holidays/data/hr_holidays_data.xml +++ b/addons/hr_holidays/data/hr_holidays_data.xml @@ -3,21 +3,13 @@ - + Paid Time Off fixed + both black - - - - - - - Home Workings - lightgreen - - + @@ -33,19 +25,20 @@ Compensatory Days fixed_allocation + both hour lavender - - + Unpaid - fixed_allocation + no both brown + hour diff --git a/addons/hr_holidays/data/hr_holidays_demo.xml b/addons/hr_holidays/data/hr_holidays_demo.xml index dd66836248e..1199aea6686 100644 --- a/addons/hr_holidays/data/hr_holidays_demo.xml +++ b/addons/hr_holidays/data/hr_holidays_demo.xml @@ -3,29 +3,23 @@ - - Overtime Compensation - fixed_allocation - blue - hour - - - Parental Time Off fixed brown both + - - - fixed - wheat - - - + + + Home Workings + lightgreen + + + + @@ -34,7 +28,7 @@ 20 - confirm + validate @@ -45,13 +39,6 @@ confirm - - Overtime Compensation - - 20 - - - Summer Vacation @@ -59,25 +46,21 @@ + + Compensation + + 12 + + validate + + - - - - - - @@ -118,7 +101,7 @@ 20 - confirm + validate @@ -136,12 +119,6 @@ - - - 20 - confirm - - - - Overtime Compensation - - 5 - + validate @@ -218,20 +188,6 @@ - - - - - - - - Trip to Paris @@ -255,25 +211,7 @@ - - Sleeping day - - - - - - 3 - - confirm - - - - - 20 - confirm - - - - Overtime Compensation - - 5 - + validate @@ -306,12 +237,6 @@ - - - 20 - confirm + validate @@ -376,13 +301,6 @@ confirm - - Overtime Compensation - - 10 - - - Summer Vacation @@ -391,49 +309,14 @@ - - - - - - - - Trip to NY - - - - - - 3 - - confirm - - - Trip to Barcelona - - - - - - 3 - - confirm - - Home working @@ -452,20 +335,6 @@ ('id', 'in', [ref('hr_holidays.hr_holidays_sl_fpi')]) ]"/> - - - - - - - - @@ -474,14 +343,7 @@ 20 - confirm - - - - Overtime Compensation - - 10 - + validate @@ -492,18 +354,6 @@ - - - - - - confirm - - Overtime Compensation - - 10 - - - Summer Vacation diff --git a/addons/hr_holidays/models/hr_employee.py b/addons/hr_holidays/models/hr_employee.py index b32817f507a..522b4655b28 100644 --- a/addons/hr_holidays/models/hr_employee.py +++ b/addons/hr_holidays/models/hr_employee.py @@ -10,14 +10,9 @@ from odoo.tools.float_utils import float_round class HrEmployeeBase(models.AbstractModel): _inherit = "hr.employee.base" - def _group_hr_user_domain(self): - group = self.env.ref('hr_holidays.group_hr_holidays_team_leader', raise_if_not_found=False) - return [('groups_id', 'in', group.ids)] if group else [] - leave_manager_id = fields.Many2one( 'res.users', string='Time Off', - domain=_group_hr_user_domain, - help="User responsible of leaves approval. Should be Team Leader or Department Manager.") + help="User responsible of leaves approval.") remaining_leaves = fields.Float( compute='_compute_remaining_leaves', string='Remaining Paid Time Off', help='Total number of paid time off allocated to this employee, change this value to create allocation/time off request. ' @@ -128,7 +123,7 @@ class HrEmployeeBase(models.AbstractModel): super(HrEmployeeBase, self)._onchange_parent_id() previous_manager = self._origin.parent_id.user_id manager = self.parent_id.user_id - if manager and manager.has_group('hr.group_hr_user') and (self.leave_manager_id == previous_manager or not self.leave_manager_id): + if manager and self.leave_manager_id == previous_manager: self.leave_manager_id = manager def _compute_show_leaves(self): @@ -148,10 +143,20 @@ class HrEmployeeBase(models.AbstractModel): ]) return [('id', 'in', holidays.mapped('employee_id').ids)] + @api.model + def create(self, values): + if 'parent_id' in values: + manager = self.env['hr.employee'].browse(values['parent_id']).user_id + values['leave_manager_id'] = values.get('leave_manager_id', manager.id) + return super(HrEmployeeBase, self).create(values) + def write(self, values): + if 'parent_id' in values: + manager = self.env['hr.employee'].browse(values['parent_id']).user_id + values['leave_manager_id'] = values.get('leave_manager_id', manager.id) res = super(HrEmployeeBase, self).write(values) - today_date = fields.Datetime.now() if 'parent_id' in values or 'department_id' in values: + today_date = fields.Datetime.now() hr_vals = {} if values.get('parent_id') is not None: hr_vals['manager_id'] = values['parent_id'] diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 05b172894f0..1f87ad0a7e0 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -9,7 +9,7 @@ import math from datetime import datetime, time from pytz import timezone, UTC -from odoo import api, fields, models, tools +from odoo import api, fields, models, SUPERUSER_ID, tools from odoo.addons.resource.models.resource import float_to_time, HOURS_PER_DAY from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools import float_compare @@ -32,9 +32,6 @@ class HolidaysRequest(models.Model): - can discuss on its leave requests; - can reset only its own leaves; - cannot validate any leaves; - - a Team Leader - - has same rights as a regular employee - - but is able to approve employees' leaves who are in his/her team (see Employee.leave_manager_id in hr_holidays/hr.py) - an Officer - can see all leaves; - can validate "HR" single validation leaves from people if @@ -69,9 +66,10 @@ class HolidaysRequest(models.Model): defaults = self._default_get_request_parameters(defaults) LeaveType = self.env['hr.leave.type'].with_context(employee_id=defaults.get('employee_id'), default_date_from=defaults.get('date_from', fields.Datetime.now())) - lt = LeaveType.search([('valid', '=', True)]) + lt = LeaveType.search([('valid', '=', True)], limit=1) - defaults['holiday_status_id'] = lt[0].id if len(lt) > 0 else defaults.get('holiday_status_id') + defaults['holiday_status_id'] = lt.id if lt else defaults.get('holiday_status_id') + defaults['state'] = 'confirm' if lt and lt.validation_type != 'no_validation' else 'draft' return defaults def _default_employee(self): @@ -105,7 +103,7 @@ class HolidaysRequest(models.Model): ('refuse', 'Refused'), ('validate1', 'Second Approval'), ('validate', 'Approved') - ], string='Status', readonly=True, tracking=True, copy=False, default='confirm', + ], string='Status', readonly=True, tracking=True, copy=False, help="The status is set to 'To Submit', when a time off request is created." + "\nThe status is 'To Approve', when time off request is confirmed by user." + "\nThe status is 'Refused', when time off request is refused by manager." + @@ -113,6 +111,7 @@ class HolidaysRequest(models.Model): payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the time off has been taken into account in the payslip.') report_note = fields.Text('HR Comments') user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) + manager_id = fields.Many2one('hr.employee') # leave type configuration holiday_status_id = fields.Many2one( "hr.leave.type", string="Time Off Type", required=True, readonly=True, @@ -123,7 +122,6 @@ class HolidaysRequest(models.Model): employee_id = fields.Many2one( 'hr.employee', string='Employee', index=True, readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, tracking=True) - manager_id = fields.Many2one('hr.employee', string='Manager', readonly=True) department_id = fields.Many2one( 'hr.department', string='Department', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) @@ -263,6 +261,7 @@ class HolidaysRequest(models.Model): self.request_unit_half = False self.request_unit_hours = False self.request_unit_custom = False + self.state = 'confirm' if self.validation_type != 'no_validation' else 'draft' @api.onchange('user_id') def _onchange_user_id(self): @@ -472,8 +471,7 @@ class HolidaysRequest(models.Model): if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.allocation_type == 'no': continue leave_days = holiday.holiday_status_id.get_days(holiday.employee_id.id)[holiday.holiday_status_id.id] - if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or \ - float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1: + if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1: raise ValidationError(_('The number of remaining time off is not sufficient for this time off type.\n' 'Please also check the time off waiting for validation.')) @@ -555,32 +553,59 @@ class HolidaysRequest(models.Model): _('You can take %s until %s') % ( leave.holiday_status_id.display_name, leave.holiday_status_id.validity_stop)) + def _check_double_validation_rules(self, employee, state): + if self.user_has_groups('hr_holidays.group_hr_holidays_manager'): + return + + if state == 'validate1': + if employee.leave_manager_id != self.env.user: + raise AccessError(_('You cannot first approve a leave for %s, because you are not his leave manager' % (employee.name,))) + elif state == 'validate' and not self.user_has_groups('hr_holidays.group_hr_holidays_user'): + # Is probably handled via ir.rule + raise AccessError(_('You don\'t have the rights to apply second approval on a leave request')) + @api.model def create(self, values): """ Override to avoid automatic logging of creation """ - employee_id = values.get('employee_id', False) - leave_type_id = values.get('holiday_status_id') - leave_type = self.env['hr.leave.type'].browse(leave_type_id) - if leave_type.validation_type == 'no_validation': - values.update({'state': 'confirm'}) + if not self._context.get('leave_fast_create'): + employee_id = values.get('employee_id', False) + leave_type_id = values.get('holiday_status_id') + leave_type = self.env['hr.leave.type'].browse(leave_type_id) + # Handle automatic department_id + if not values.get('department_id'): + values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id}) + + # Handle no_validation + if leave_type.validation_type == 'no_validation': + values.update({'state': 'confirm'}) + + # Handle double validation + if leave_type.validation_type == 'both': + self._check_double_validation_rules(employee_id, values.get('state', False)) - if not values.get('department_id'): - values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id}) holiday = super(HolidaysRequest, self.with_context(mail_create_nosubscribe=True)).create(values) if not self._context.get('leave_fast_create'): - holiday.add_follower(employee_id) - if holiday.validation_type == 'hr': - holiday.message_subscribe(partner_ids=(holiday.employee_id.parent_id.user_id.partner_id | holiday.employee_id.leave_manager_id.partner_id).ids) + # FIXME remove these, as they should not be needed if employee_id: holiday._sync_employee_details() if 'number_of_days' not in values and ('date_from' in values or 'date_to' in values): holiday._onchange_leave_dates() + + # Everything that is done here must be done using sudo because we might + # have different create and write rights + # eg : holidays_user can create a leave request with validation_type = 'manager' for someone else + # but they can only write on it if they are leave_manager_id + holiday_sudo = holiday.sudo() + holiday_sudo.add_follower(employee_id) + if holiday.validation_type == 'manager': + holiday_sudo.message_subscribe(partner_ids=holiday.employee_id.leave_manager_id.partner_id.ids) if leave_type.validation_type == 'no_validation': - holiday.sudo().action_validate() - holiday.message_subscribe(partner_ids=[holiday._get_responsible_for_approval().partner_id.id]) - holiday.sudo().message_post(body=_("The time off has been automatically approved"), subtype="mt_comment") # Message from OdooBot (sudo) + # Automatic validation should be done in sudo, because user might not have the rights to do it by himself + holiday_sudo.action_validate() + holiday_sudo.message_subscribe(partner_ids=[holiday._get_responsible_for_approval().partner_id.id]) + holiday_sudo.message_post(body=_("The time off has been automatically approved"), subtype="mt_comment") # Message from OdooBot (sudo) elif not self._context.get('import_file'): - holiday.activity_update() + holiday_sudo.activity_update() return holiday def _read_from_database(self, field_names, inherited_field_names=[]): @@ -609,6 +634,8 @@ class HolidaysRequest(models.Model): employee_id = values.get('employee_id', False) if not self.env.context.get('leave_fast_create') and values.get('state'): self._check_approval_update(values['state']) + if any(holiday.validation_type == 'both' for holiday in self): + self._check_double_validation_rules(self.env['hr.employee'].browse(values.get('employee_id', self.employee_id.id)), values['state']) result = super(HolidaysRequest, self).write(values) if not self.env.context.get('leave_fast_create'): for holiday in self: @@ -652,7 +679,7 @@ class HolidaysRequest(models.Model): 'calendar_id': leave.employee_id.resource_calendar_id.id, 'time_type': leave.holiday_status_id.time_type, }) - return self.env['resource.calendar.leaves'].create(vals_list) + return self.env['resource.calendar.leaves'].sudo().create(vals_list) def _remove_resource_leave(self): """ This method will create entry in resource calendar time off object at the time of holidays cancel/removed """ @@ -737,6 +764,14 @@ class HolidaysRequest(models.Model): 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}) + + + # Post a second message, more verbose than the tracking message + for holiday in self.filtered(lambda holiday: holiday.employee_id.user_id): + holiday.message_post( + body=_('Your %s planned on %s has been accepted' % (holiday.holiday_status_id.display_name, holiday.date_from)), + partner_ids=holiday.employee_id.user_id.partner_id.ids) + self.filtered(lambda hol: not hol.validation_type == 'both').action_validate() if not self.env.context.get('leave_fast_create'): self.activity_update() @@ -795,6 +830,13 @@ class HolidaysRequest(models.Model): holiday.meeting_id.unlink() # If a category that created several holidays, cancel all related holiday.linked_request_ids.action_refuse() + + # Post a second message, more verbose than the tracking message + if holiday.employee_id.user_id: + holiday.message_post( + body=_('Your %s planned on %s has been refused' % (holiday.holiday_status_id.display_name, holiday.date_from)), + partner_ids=holiday.employee_id.user_id.partner_id.ids) + self._remove_resource_leave() self.activity_update() return True @@ -826,39 +868,23 @@ class HolidaysRequest(models.Model): 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') and holiday.holiday_type == 'employee': - manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id - team_leader = holiday.employee_id.leave_manager_id - error = False - - if not manager and not team_leader: - error = not is_officer - else: - error = (not (manager and manager == current_employee) and not (team_leader and team_leader == self.env.user)) - - if error: + if not is_officer and self.env.user != holiday.employee_id.leave_manager_id: 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': - raise UserError(_('Only an Leave Manager can apply the second approval on leave requests.')) - # ------------------------------------------------------------ # Activity methods # ------------------------------------------------------------ def _get_responsible_for_approval(self): self.ensure_one() - responsible = self.env['res.users'] + responsible = self.env['res.users'].browse(SUPERUSER_ID) - if self.validation_type == 'hr' or (self.validation_type == 'both' and self.state == 'validate1'): + if self.validation_type == 'manager' or (self.validation_type == 'both' and self.state == 'confirm'): + if self.employee_id.leave_manager_id: + responsible = self.employee_id.leave_manager_id + elif self.validation_type == 'hr' or (self.validation_type == 'both' and self.state == 'validate1'): if self.holiday_status_id.responsible_id: responsible = self.holiday_status_id.responsible_id - elif self.state == 'confirm' or (self.state == 'validate' and self.validation_type == 'no_validation'): - if self.employee_id.leave_manager_id and self.employee_id.leave_manager_id.has_group('hr_holidays.group_hr_holidays_team_leader'): - responsible = self.employee_id.leave_manager_id - elif self.employee_id.parent_id.user_id and self.employee_id.parent_id.user_id.has_group('hr_holidays.group_hr_holidays_team_leader'): - responsible = self.employee_id.parent_id.user_id - elif self.department_id.manager_id.user_id: - responsible = self.department_id.manager_id.user_id return responsible @@ -912,7 +938,7 @@ class HolidaysRequest(models.Model): ref_action = self._notify_get_action_link('controller', controller='/leave/refuse') hr_actions += [{'url': ref_action, 'title': _('Refuse')}] - holiday_user_group_id = self.env.ref('hr_holidays.group_hr_holidays_team_leader').id + holiday_user_group_id = self.env.ref('hr_holidays.group_hr_holidays_user').id new_group = ( 'group_hr_holidays_user', lambda pdata: pdata['type'] == 'user' and holiday_user_group_id in pdata['groups'], { 'actions': hr_actions, diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py index ac890454a9e..cb3cbe34a0f 100644 --- a/addons/hr_holidays/models/hr_leave_type.py +++ b/addons/hr_holidays/models/hr_leave_type.py @@ -20,7 +20,12 @@ _logger = logging.getLogger(__name__) class HolidaysType(models.Model): _name = "hr.leave.type" _description = "Time Off Type" - _order = "sequence, id" + + @api.model + def _model_sorting_key(self, leave_type): + remaining = leave_type.virtual_remaining_leaves > 0 + taken = leave_type.leaves_taken > 0 + return leave_type.allocation_type == 'fixed' and remaining, leave_type.allocation_type == 'fixed_allocation' and remaining, taken name = fields.Char('Time Off Type', required=True, translate=True) code = fields.Char('Code') @@ -63,9 +68,9 @@ class HolidaysType(models.Model): group_days_leave = fields.Float( compute='_compute_group_days_leave', string='Group Time Off') company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company) - responsible_id = fields.Many2one('res.users', 'Responsible', domain=lambda self: [('groups_id', 'in', self.env.ref('hr_holidays.group_hr_holidays_user').id)], - help="This user will be responsible for approving this type of times off" - "This is only used when validation is 'hr' or 'both'",) + responsible_id = fields.Many2one('res.users', 'Responsible', + help="This user will be responsible for approving this type of times off" + "This is only used when validation is 'hr' or 'both'",) validation_type = fields.Selection([ ('no_validation', 'No Validation'), ('hr', 'Time Off Officer'), @@ -198,16 +203,14 @@ class HolidaysType(models.Model): @api.model def get_days_all_request(self): - employee_id = self._get_contextual_employee_id() - - leaves_type = self.search([]) - leaves = leaves_type.get_days(employee_id) - leave_id_name = dict(zip(leaves_type.ids, leaves_type.mapped('name'))) - leave_id_allocation_type = dict(zip(leaves_type.ids, leaves_type.mapped('allocation_type'))) - result = [(leave_id_name[leave_id], {key: round(value, 2) for (key, value) in leave.items()}, leave_id_allocation_type[leave_id]) for leave_id, leave in leaves.items() if leave['virtual_remaining_leaves'] or leave['max_leaves']] - - sort_key = lambda l: (l[2] == 'fixed', l[2] == 'fixed_allocation', l[1]['virtual_remaining_leaves']) - return sorted(result, key=sort_key, reverse=True) + leave_types = sorted(self.search([]).filtered(lambda x: x.virtual_remaining_leaves or x.max_leaves), key=self._model_sorting_key, reverse=True) + return [(lt.name, { + 'remaining_leaves': lt.remaining_leaves, + 'virtual_remaining_leaves': lt.virtual_remaining_leaves, + 'max_leaves': lt.max_leaves, + 'leaves_taken': lt.leaves_taken, + }, lt.allocation_type) + for lt in leave_types] def _get_contextual_employee_id(self): if 'employee_id' in self._context: @@ -298,8 +301,8 @@ class HolidaysType(models.Model): leave_ids = super(HolidaysType, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) if not count and not order and employee_id: leaves = self.browse(leave_ids) - sort_key = lambda l: (l.allocation_type in ['fixed', 'fixed_allocation'], l.virtual_remaining_leaves) - return leaves.sorted(key=sort_key, reverse=True).ids + + return leaves.sorted(key=self._model_sorting_key, reverse=True).ids return leave_ids def action_see_days_allocated(self): diff --git a/addons/hr_holidays/security/hr_holidays_security.xml b/addons/hr_holidays/security/hr_holidays_security.xml index 623438454b4..144ce4c9600 100644 --- a/addons/hr_holidays/security/hr_holidays_security.xml +++ b/addons/hr_holidays/security/hr_holidays_security.xml @@ -5,16 +5,10 @@ 10 - - Team Approver - - - - All Approver - + @@ -29,7 +23,7 @@ - Time Off: employee: read all + Time Off base.group_user read [(1, '=', 1)] @@ -39,35 +33,53 @@ - Time Off: employee: create/write/unlink not validated own + Time Off base.group_user create/write - [('employee_id.user_id','=',user.id), ('state', 'in', ['draft', 'confirm', 'cancel', 'refuse']), ('holiday_type', '=', 'employee')] + [ + ('holiday_type', '=', 'employee'), + '|', + '&', + ('employee_id.user_id', '=', user.id), + ('state', 'not in', ['validate', 'validate1']), + '&', + ('validation_type', 'in', ['manager', 'both', 'no_validation']), + ('employee_id.leave_manager_id', '=', user.id), + ] + - - Time Off: approver: write on employee's time off in his team + + Time Off base.group_user unlink - ['&', ('holiday_type', '=', 'employee'), '|', ('employee_id.leave_manager_id', '=' , user.id), ('employee_id.parent_id.user_id', '=' , user.id)] + [('employee_id.user_id', '=', user.id), ('state', '=', 'draft')] + - - + - Time Off: see all time off: create/write/unlink own + managed employee + without department + same or managed department + Time Off All Approver create/write - ['|', -'|', ('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)] + [ + ('holiday_type', '=', 'employee'), + '|', + '&', + ('employee_id.user_id', '=', user.id), + ('state', '!=', 'validate'), + '|', + ('employee_id.user_id', '!=', user.id), + ('employee_id.user_id', '=', False) + ] + - Time Off: administrator: no limit + Time Off Administrator [(1, '=', 1)] @@ -83,7 +95,11 @@ Allocations: employee: read own - [('employee_id.user_id', '=', user.id)] + [ + '|', + ('employee_id.leave_manager_id', '=', user.id), + ('employee_id.user_id', '=', user.id) + ] @@ -124,21 +140,22 @@ - - Allocations: Team Leader: read employee's allocations in his team - - ['|', ('employee_id.leave_manager_id', '=' , user.id), ('employee_id.parent_id.user_id', '=' , user.id)] - - - - - - - + Time Off Resources: Approver [(1,'=',1)] - + + + + + + + + + Time Off Resources: All Approver + + [(1,'=',1)] + diff --git a/addons/hr_holidays/static/src/js/time_off_calendar.js b/addons/hr_holidays/static/src/js/time_off_calendar.js index 89e3fed2cc1..c4b27814e1e 100644 --- a/addons/hr_holidays/static/src/js/time_off_calendar.js +++ b/addons/hr_holidays/static/src/js/time_off_calendar.js @@ -3,6 +3,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { var core = require('web.core'); var CalendarController = require("web.CalendarController"); + var CalendarRenderer = require("web.CalendarRenderer"); var CalendarView = require("web.CalendarView"); var viewRegistry = require('web.view_registry'); @@ -12,36 +13,10 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { var TimeOffCalendarController = CalendarController.extend({ events: _.extend({}, CalendarController.prototype.events, { - 'click .o_header_calendar_button_prev': '_onPrev', - 'click .o_header_calendar_button_next': '_onNext', 'click .btn-time-off': '_onNewTimeOff', 'click .btn-allocation': '_onNewAllocation', }), - /** - * @override - */ - start: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - self.$el.find('.o_calendar_sidebar_container').remove(); - self._renderSummaryTimeOff(); - self.$(".o_calendar_widget").fullCalendar('option', 'contentHeight', 500); - }); - }, - - /** - * Override to add correct date in header of calendar - * - * @override - */ - update: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - self._updateSummaryTimeOff(); - }); - }, - //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- @@ -54,10 +29,12 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { */ renderButtons: function ($node) { - this.$buttons = $(QWeb.render('hr_holidays.dashboard.calendar.button', { + this._super.apply(this, arguments); + + $(QWeb.render('hr_holidays.dashboard.calendar.button', { time_off: _t('New Time Off Request'), request: _t('New Allocation Request'), - })); + })).appendTo(this.$buttons); if ($node) { this.$buttons.appendTo($node); @@ -66,79 +43,6 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { } }, - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Create a header for calendar with previous and next month button. - * Add month and year on the header - * - * @private - */ - _renderCalendarHeader: function () { - this.$el.find('.fc-week-number').remove(); - - if (!this.$('#header_calendar_leaves').length) { - var current_month = this.model.data.target_date.format('MMMM YYYY'); - var header = $(QWeb.render('hr_holidays.dashboard.calendar.header', { - current_month: current_month, - })); - - this.$el.find('.fc-widget-header').find('thead').prepend(header); - } - }, - - /** - * Create a summary of all time off and requests allocations - * - * @private - */ - _renderSummaryTimeOff: function () { - var leave_state = '
'; - leave_state += '
'; - this.$el.find('.o_calendar_container').before( - $(leave_state) - ); - - this._updateSummaryTimeOff(); - }, - - /** - * Create a summary of all time off and requests allocations - * - * @private - */ - _updateSummaryTimeOff: function () { - var self = this; - - this._renderCalendarHeader(); - - return this._rpc({ - model: 'hr.leave.type', - method: 'get_days_all_request', - context: this.context, - }) - .then(function (leaves) { - $('#counter_leaves').children().remove(); - for (var leave in leaves){ - var leave_box = $('

'); - var content = leaves[leave][1]; - var leave_count = leave_box.find('h1'); - leave_box.append(leave_count.text( - leaves[leave][2] !== 'no' ? content.virtual_remaining_leaves + " / " + content.max_leaves : -content.virtual_remaining_leaves - )); - var leave_name = leave_box.find('h3'); - leave_name.text(leaves[leave][0]); - leave_box.append(leave_name); - $('#counter_leaves').append(leave_box); - } - $('#counter_leaves').children().last().removeClass('border-right'); - - self.$el.find('.o_calendar_container').appendTo(self.$el.find('.o_form_sheet')); - }); - }, - //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- @@ -177,11 +81,30 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { }); }, }); + var TimeOffCalendarRenderer = CalendarRenderer.extend({ + _render: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + return self._rpc({ + model: 'hr.leave.type', + method: 'get_days_all_request', + context: self.context, + }); + }).then(function (result) { + self.$el.parent().find('.o_timeoff_container').remove(); + var elem = QWeb.render('hr_holidays.dashboard_calendar_header', { + timeoffs: result, + }); + self.$el.before(elem); + }); + }, + }); var TimeOffCalendarView = CalendarView.extend({ config: _.extend({}, CalendarView.prototype.config, { Controller: TimeOffCalendarController, + Renderer: TimeOffCalendarRenderer, }), }); viewRegistry.add('time_off_calendar', TimeOffCalendarView); -}); \ No newline at end of file +}); diff --git a/addons/hr_holidays/static/src/scss/time_off.scss b/addons/hr_holidays/static/src/scss/time_off.scss new file mode 100644 index 00000000000..e7efa3e1604 --- /dev/null +++ b/addons/hr_holidays/static/src/scss/time_off.scss @@ -0,0 +1,23 @@ +.o_content { + .o_timeoff_card { + border-right: #adb5bd solid 2px; + text-align: center; + margin-top: 8px; + } + + .o_timeoff_card_last { + border-right: 0px; + } + + .o_timeoff_big { + font-size: 50px; + } + + .o_timeoff_purple { + color: $o-enterprise-color; + } + + .o_timeoff_green { + color: $o-enterprise-primary-color; + } +} diff --git a/addons/hr_holidays/static/src/xml/time_off_calendar.xml b/addons/hr_holidays/static/src/xml/time_off_calendar.xml index 8e7746c228f..24ecac05586 100644 --- a/addons/hr_holidays/static/src/xml/time_off_calendar.xml +++ b/addons/hr_holidays/static/src/xml/time_off_calendar.xml @@ -1,18 +1,30 @@ - - - - - -

- - + +
+
+ + + + + + + +
+ + / Days + + + Days + +
+ + + + remaining +
+
diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py index 510c6883b41..7fed7fa4b33 100644 --- a/addons/hr_holidays/tests/common.py +++ b/addons/hr_holidays/tests/common.py @@ -20,12 +20,6 @@ class TestHrHolidaysBase(common.TransactionCase): self.user_employee = mail_new_test_user(self.env, login='david', groups='base.group_user') self.user_employee_id = self.user_employee.id - self.user_hrmanager_2 = mail_new_test_user(self.env, login='florence', groups='base.group_user,hr_holidays.group_hr_holidays_manager') - self.user_hrmanager_2_id = self.user_hrmanager_2.id - - self.user_team_leader = mail_new_test_user(self.env, login='arthur', groups='base.group_user,hr_holidays.group_hr_holidays_team_leader') - self.user_team_leader_id = self.user_team_leader.id - # Hr Data Department = self.env['hr.department'].with_context(tracking_disable=True) @@ -54,13 +48,8 @@ class TestHrHolidaysBase(common.TransactionCase): 'name': 'Bastien HrManager', 'user_id': self.user_hrmanager_id, 'department_id': self.hr_dept.id, + 'parent_id': self.employee_hruser_id, }) self.employee_hrmanager_id = self.employee_hrmanager.id - self.employee_hrmanager_2_id = self.env['hr.employee'].create({ - 'name': 'Florence HrManager', - 'user_id': self.user_hrmanager_2_id, - 'parent_id': self.employee_hrmanager_id, - }).id - self.rd_dept.write({'manager_id': self.employee_hruser_id}) diff --git a/addons/hr_holidays/tests/test_access_rights.py b/addons/hr_holidays/tests/test_access_rights.py index d206031a71f..3986668f9f7 100644 --- a/addons/hr_holidays/tests/test_access_rights.py +++ b/addons/hr_holidays/tests/test_access_rights.py @@ -6,13 +6,12 @@ from dateutil.relativedelta import relativedelta from odoo import tests from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase -from odoo.exceptions import AccessError, ValidationError, UserError +from odoo.exceptions import AccessError, UserError from odoo.tools import mute_logger @tests.tagged('access_rights', 'post_install', '-at_install') class TestLeavesRights(TestHrHolidaysBase): - def setUp(self): super(TestLeavesRights, self).setUp() self.leave_type = self.env['hr.leave.type'].create({ @@ -34,22 +33,409 @@ class TestLeavesRights(TestHrHolidaysBase): 'number_of_days': 1, }) + self.lt_no_validation = self.env['hr.leave.type'].create({ + 'name': 'Validation = no_validation', + 'validation_type': 'hr', + 'allocation_type': 'no', + 'validity_start': False, + }) + + self.lt_validation_hr = self.env['hr.leave.type'].create({ + 'name': 'Validation = HR', + 'validation_type': 'hr', + 'allocation_type': 'no', + 'validity_start': False, + }) + + self.lt_validation_manager = self.env['hr.leave.type'].create({ + 'name': 'Validation = manager', + 'validation_type': 'hr', + 'allocation_type': 'no', + 'validity_start': False, + }) + + self.lt_validation_both = self.env['hr.leave.type'].create({ + 'name': 'Validation = both', + 'validation_type': 'hr', + 'allocation_type': 'no', + 'validity_start': False, + }) + + self.draft_status = [ + self.lt_validation_hr, + self.lt_validation_manager, + self.lt_validation_both + ] + + self.confirm_status = [ + self.lt_no_validation, + self.lt_validation_hr, + self.lt_validation_manager, + self.lt_validation_both + ] + + def request_leave(self, user_id, date_from, number_of_days, values=None): + values = dict(values or {}, **{ + 'date_from': date_from, + 'date_to': date_from + relativedelta(days=number_of_days), + 'number_of_days': number_of_days, + }) + return self.env['hr.leave'].with_user(user_id).create(values) + + +@tests.tagged('access_rights', 'access_rights_states') +class TestAcessRightsStates(TestLeavesRights): + # ****************************************************** + # Action draft + # ****************************************************** + + def test_draft_status(self): + """ + We should only be able to draft a leave that is + in confirm or refuse state + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Ranoi', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.action_draft() + + values = { + 'name': 'Ranoi', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': status.id, + 'state': 'draft' + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=20 + i), 1, values) + with self.assertRaises(UserError): + leave.action_draft() + + def test_base_user_draft_his_leave(self): + """ + Should be able to draft his own leave + whatever the holiday_status_id + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_employee.id).action_draft() + + def test_base_user_draft_other_employee_leave(self): + """ + Should not be able to draft the leave of someone else + whatever the holiday_status_id + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hruser.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + with self.assertRaises(UserError): + leave.with_user(self.user_employee.id).action_draft() + + def test_base_user_draft_other_employee_leave_and_is_leave_manager_id(self): + """ + Should not be able to draft the leave of someone else + even when being the leave manager id for this person + whatever the holiday_status_id + """ + self.employee_hruser.write({'leave_manager_id': self.user_employee.id}) + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hruser.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + with self.assertRaises(UserError): + leave.with_user(self.user_employee.id).action_draft() + + def test_base_user_draft_self_and_is_leave_manager_id(self): + """ + Should be able to draft his own leave + even when being leave manager id + whatever the holiday_status_id + """ + self.employee_emp.write({'leave_manager_id': self.user_employee.id}) + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_employee.id).action_draft() + + def test_holiday_user_draft_his_leave(self): + """ + Should be able to draft his own leave + whatever the holidays_status_id + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hruser.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_hruser.id).action_draft() + + def test_holiday_user_draft_other_employee_leave(self): + """ + Should not be able to draft other employee leave + whatever the holiday_status_id + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + with self.assertRaises(UserError): + leave.with_user(self.user_hruser.id).action_draft() + + def test_holiday_user_draft_other_employee_leave_and_is_leave_manager_id(self): + """ + Should not be able to draft other employee leave + even if he is the leave manager id + whatever the holiday_status_id + """ + self.employee_emp.write({'leave_manager_id': self.user_hruser.id}) + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + with self.assertRaises(UserError): + leave.with_user(self.user_hruser.id).action_draft() + + def test_holiday_user_draft_self_and_is_manager_id(self): + """ + Should be able to draft his own leave + even if he is leave manager id + whatever the holiday_status_id + """ + self.employee_hruser.write({'leave_manager_id': self.user_hruser.id}) + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hruser.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_hruser.id).action_draft() + + def test_holiday_manager_draft_his_leave(self): + """ + The holiday manager should be able to do everything + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hrmanager.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_hrmanager.id).action_draft() + + def test_holiday_manager_draft_other_employee_leave(self): + """ + The holiday manager should be able to do everything + """ + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hruser.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_hrmanager.id).action_draft() + + def test_holiday_manager_draft_other_employee_leave_and_is_leave_manager_id(self): + """ + The holiday manager should be able to do everything + """ + self.employee_hruser.write({'leave_manager_id': self.user_hrmanager.id}) + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hruser.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_hrmanager.id).action_draft() + + def test_holiday_manager_draft_self_and_is_manager_id(self): + """ + The holiday manager should be able to do everything + """ + self.employee_hrmanager.write({'leave_manager_id': self.user_hrmanager.id}) + for i, status in enumerate(self.draft_status): + values = { + 'name': 'Random Leave', + 'employee_id': self.employee_hrmanager.id, + 'holiday_status_id': status.id, + } + leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values) + leave.with_user(self.user_hrmanager.id).action_draft() + +@tests.tagged('access_rights', 'access_rights_create') +class TestAccessRightsCreate(TestLeavesRights): @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.with_user(self.user_employee_id).message_post( - body='I haz messaging', - subtype='mail.mt_comment', - message_type='comment' - ) + def test_base_user_create_self(self): + """ A simple user can create a leave for himself """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + } + self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values) - self.employee_leave.with_user(self.user_hrmanager_id).action_approve() + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_base_user_create_other(self): + """ A simple user cannot create a leave for someone else """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_hruser_id, + 'holiday_status_id': self.leave_type.id, + } + with self.assertRaises(AccessError): + self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values) - self.employee_leave.with_user(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_base_user_create_validate(self): + """ A simple user cannot create a leave in validate state """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + 'state': 'validate', + } + with self.assertRaises(AccessError): + self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_base_user_create_batch(self): + """ A simple user cannot create a leave in bacth mode (by company, by department, by tag)""" + values = { + 'name': 'Hol10', + 'holiday_status_id': self.leave_type.id, + 'holiday_type': 'company', + 'mode_company_id': 1, + } + with self.assertRaises(AccessError): + self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values) + + # hr_holidays.group_hr_holidays_user + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_user_create_self(self): + """ A holidays user can create a leave for himself """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_hruser_id, + 'holiday_status_id': self.leave_type.id, + } + self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_user_create_other(self): + """ A holidays user can create a leave for someone else """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + } + self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_user_create_validate(self): + """ A holidays user can create a leave in validate state but not for himself """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + 'state': 'validate', + } + self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values) + values.update(employee_id=self.employee_hruser_id) + with self.assertRaises(AccessError): + self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_user_create_batch(self): + """ A holidays user cannot create a leave in bacth mode (by company, by department, by tag)""" + values = { + 'name': 'Hol10', + 'holiday_status_id': self.leave_type.id, + 'holiday_type': 'company', + 'mode_company_id': 1, + } + with self.assertRaises(AccessError): + self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values) + + # hr_holidays.group_hr_holidays_manager + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_manager_create_self(self): + """ A holidays manager can create a leave for himself """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_hrmanager_id, + 'holiday_status_id': self.leave_type.id, + } + self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_manager_create_other(self): + """ A holidays manager can create a leave for someone else """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + } + self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_manager_create_validate(self): + """ A holidays manager can? create a leave in validate state even for himself """ + values = { + 'name': 'Hol10', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + 'state': 'validate', + } + self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values) + values.update(employee_id=self.employee_hruser_id) + self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values) + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_holidays_manager_create_batch(self): + """ A holidays manager can create a leave in bacth mode (by company, by department, by tag)""" + values = { + 'name': 'Hol10', + 'holiday_status_id': self.leave_type.id, + 'holiday_type': 'company', + 'mode_company_id': 1, + } + self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values) + + +@tests.tagged('access_rights', 'access_rights_read') +class TestAccessRightsRead(TestLeavesRights): + # base.group_user @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_leave_read_by_user_other(self): @@ -92,19 +478,18 @@ class TestLeavesRights(TestHrHolidaysBase): res = self.employee_leave.read(['name', 'number_of_days', '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') + +@tests.tagged('access_rights', 'access_rights_write') +class TestAccessRightsWrite(TestLeavesRights): + # base.group_user @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_leave_update_hr_by_user(self): + def test_leave_update_by_user(self): """ User may update its leave """ self.employee_leave.with_user(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): + def test_leave_update_by_user_other(self): """ User cannot update other people leaves """ other_leave = self.env['hr.leave'].with_user(self.user_hruser).create({ 'name': 'Test', @@ -118,10 +503,6 @@ class TestLeavesRights(TestHrHolidaysBase): with self.assertRaises(AccessError): other_leave.with_user(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'}) - # ---------------------------------------- - # Creation - # ---------------------------------------- - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_leave_creation_for_other_user(self): """ Employee cannot creates a leave request for another employee """ @@ -136,132 +517,52 @@ class TestLeavesRights(TestHrHolidaysBase): 'number_of_days': 1, }) - # ---------------------------------------- - # 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'].with_user(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': 1, - }) - manager_leave.with_user(self.user_hrmanager).action_draft() + def test_leave_messaging_by_user(self): + """ User may communicate on its own leaves, even if validated """ + self.employee_leave.with_user(self.user_employee_id).message_post( + body='I haz messaging', + subtype='mail.mt_comment', + message_type='comment' + ) - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_leave_reset_by_manager_other(self): - """ Manager may reset other leaves """ - self.employee_leave.with_user(self.user_hrmanager).action_draft() + self.employee_leave.with_user(self.user_hrmanager_id).action_approve() - @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'].with_user(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': 1, - }) - officer_leave.with_user(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.with_user(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.with_user(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'].with_user(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': 1, - }) - with self.assertRaises(UserError): - other_leave.with_user(self.user_employee_id).action_draft() + self.employee_leave.with_user(self.user_employee_id).message_post( + body='I still haz messaging', + subtype='mail.mt_comment', + message_type='comment' + ) # ---------------------------------------- # 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.assertEqual(self.employee_leave.state, 'confirm') - self.employee_leave.with_user(self.user_hrmanager_id).action_approve() - self.assertEqual(self.employee_leave.state, 'validate') + # base.group_user @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.assertEqual(self.employee_leave.state, 'confirm') - self.employee_leave.with_user(self.user_hruser).action_approve() - self.assertEqual(self.employee_leave.state, 'validate') - - @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}) - with self.assertRaises(AccessError): - self.employee_leave.with_user(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.with_user(self.user_hruser).action_approve() - self.assertEqual(self.employee_leave.state, 'confirm') - - @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.with_user(self.user_hruser).action_approve() - self.assertEqual(self.employee_leave.state, 'confirm') - - @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.assertEqual(self.employee_leave.state, 'confirm') - self.employee_leave.with_user(self.user_hruser).action_approve() - self.assertEqual(self.employee_leave.state, 'validate') - - @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 """ + def test_leave_hr_to_validate_by_user(self): + """ User may not validate any leaves in HR mode """ with self.assertRaises(UserError): self.employee_leave.with_user(self.user_employee_id).action_approve() with self.assertRaises(UserError): self.employee_leave.with_user(self.user_employee_id).write({'state': 'validate'}) + # hr_holidays.group_hr_holidays_user + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_leave_validate_by_manager(self): - """ Manager (who has no manager) validate its own leaves """ + def test_leave_hr_to_validate_by_holiday_user(self): + """ Manager can validate leaves in HR mode """ + self.assertEqual(self.employee_leave.state, 'confirm') + self.employee_leave.with_user(self.user_hrmanager_id).action_approve() + self.assertEqual(self.employee_leave.state, 'validate') + + # hr_holidays.group_hr_holidays_manager + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_hr_to_validate_by_manager(self): + """ Manager validate its own leaves """ manager_leave = self.env['hr.leave'].with_user(self.user_hrmanager_id).create({ 'name': 'Hol manager', 'holiday_status_id': self.leave_type.id, @@ -274,125 +575,115 @@ class TestLeavesRights(TestHrHolidaysBase): manager_leave.action_approve() self.assertEqual(manager_leave.state, 'validate') - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_leave_validate_by_manager_2(self): - """ Manager (who has also a manager) validate its own leaves """ - manager_leave2 = self.env['hr.leave'].with_user(self.user_hrmanager_2_id).create({ - 'name': 'Hol manager2', - 'holiday_status_id': self.leave_type.id, - 'employee_id': self.employee_hrmanager_2_id, - 'date_from': (datetime.today() + relativedelta(days=15)), - 'date_to': (datetime.today() + relativedelta(days=16)), - 'number_of_days': 1, - }) - self.assertEqual(manager_leave2.state, 'confirm') - manager_leave2.action_approve() - self.assertEqual(manager_leave2.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.with_user(self.user_hrmanager_id).action_approve() + # base.group_user @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""" + def test_leave_manager_to_validate_by_user(self): + """ A simple user can validate in manager mode if he is leave_manager_id """ 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.with_user(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}) + values = { + 'name': 'Hol HrUser', + 'employee_id': self.employee_hruser_id, + 'holiday_status_id': self.leave_type.id, + 'state': 'confirm', + } + hr_leave = self.request_leave(self.user_hruser_id, datetime.now() + relativedelta(days=2), 1, values) with self.assertRaises(AccessError): - self.employee_leave.with_user(self.user_hruser).action_approve() + hr_leave.with_user(self.user_employee_id).action_approve() + self.employee_hruser.write({'leave_manager_id': self.user_employee_id}) + hr_leave.with_user(self.user_employee_id).action_approve() + + # hr_holidays.group_hr_holidays_user @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""" + def test_leave_manager_to_validate_by_holiday_user(self): + """ A holiday user can validate in manager mode """ self.leave_type.write({'validation_type': 'manager'}) - self.employee_emp.write({'parent_id': self.employee_hruser.id}) - self.employee_leave.with_user(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.with_user(self.user_hruser).action_approve() - - with self.assertRaises(UserError): - self.employee_leave.with_user(self.user_hruser).write({'state': 'validate'}) + values = { + 'name': 'Hol HrUser', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.leave_type.id, + 'state': 'confirm', + } + hr_leave = self.request_leave(self.user_hruser_id, datetime.now() + relativedelta(days=2), 1, values) + hr_leave.with_user(self.user_hruser_id).action_approve() # ---------------------------------------- # 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 """ + def test_leave_double_validate(self): self.leave_type.write({'validation_type': 'both'}) - self.employee_leave.with_user(self.user_hrmanager_id).action_approve() - self.employee_leave.with_user(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.with_user(self.user_hruser).action_approve() - with self.assertRaises(UserError): - self.employee_leave.with_user(self.user_hruser).action_validate() - - with self.assertRaises(UserError): - self.employee_leave.with_user(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.with_user(self.user_hruser).action_approve() - self.employee_leave.with_user(self.user_hrmanager_id).action_validate() - - # ---------------------------------------- - # Validation: team leader - # ---------------------------------------- - - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_leave_validation_team_leader_other(self): - """ Team Leader validates its employee's allocations only""" - self.leave_type.write({'allocation_type': 'fixed'}) - self.env['hr.leave.allocation'].create({ + values = { + 'name': 'double HrManager', + 'employee_id': self.employee_hrmanager_id, 'holiday_status_id': self.leave_type.id, - 'employee_id': self.employee_emp.id, - 'number_of_days': 10, - }).action_approve() + 'state': 'confirm', + } + self.employee_hrmanager.leave_manager_id = self.env['res.users'].browse(1) + hr_leave = self.request_leave(self.user_hruser_id, datetime.now() + relativedelta(days=6), 1, values) + hr_user_leave = hr_leave.with_user(self.user_hruser_id) with self.assertRaises(AccessError): - self.employee_leave.with_user(self.user_team_leader).action_approve() + hr_user_leave.action_approve() + with self.assertRaises(AccessError): + hr_leave.with_user(self.user_employee_id).action_approve() + + self.employee_hrmanager.leave_manager_id = self.user_hruser + hr_leave.with_user(self.user_hruser_id).action_approve() + + with self.assertRaises(AccessError): + hr_leave.with_user(self.user_employee_id).action_validate() + hr_leave.with_user(self.user_hruser_id).action_validate() + + # hr_holidays.group_hr_holidays_manager @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_leave_validation_team_leader(self): - """ Team Leader validates its employee's allocations""" - self.leave_type.write({'allocation_type': 'fixed'}) - - # set the team leader - self.employee_emp.leave_manager_id = self.user_team_leader - - self.env['hr.leave.allocation'].create({ + def test_leave_double_validate_holiday_manager(self): + self.leave_type.write({'validation_type': 'both'}) + values = { + 'name': 'double HrManager', + 'employee_id': self.employee_emp_id, 'holiday_status_id': self.leave_type.id, - 'employee_id': self.employee_emp.id, - 'number_of_days': 10, - }).action_approve() - self.employee_leave.with_user(self.user_team_leader).action_approve() + 'state': 'confirm', + } + hr_leave = self.request_leave(self.user_hrmanager_id, datetime.now() + relativedelta(days=4), 1, values).with_user(self.user_hrmanager_id) + hr_leave.action_approve() + hr_leave.action_validate() + + # ---------------------------------------- + # State = Refuse + # ---------------------------------------- + + # base.group_user + + # hr_holidays.group_hr_holidays_user + + # TODO Can refuse + + # hr_holidays.group_hr_holidays_manager + + # TODO Can refuse + + # ---------------------------------------- + # State = Cancel + # ---------------------------------------- + + # base.group_user + + # TODO Can Cancel if start_date in the future + + # hr_holidays.group_hr_holidays_user + + # TODO Can Cancel if not in validate + + # hr_holidays.group_hr_holidays_manager + + # TODO Can always cancel with great powers comes great responbilities class TestMultiCompany(TestHrHolidaysBase): @@ -426,7 +717,7 @@ class TestMultiCompany(TestHrHolidaysBase): employee_leave = self.employee_leave.with_user(self.user_employee) with self.assertRaises(AccessError): - name = employee_leave.name + employee_leave.name with self.assertRaises(AccessError): employee_leave.action_approve() @@ -436,7 +727,7 @@ class TestMultiCompany(TestHrHolidaysBase): employee_leave_hruser = self.employee_leave.with_user(self.user_hruser) with self.assertRaises(AccessError): - name = employee_leave_hruser.name + employee_leave_hruser.name with self.assertRaises(AccessError): employee_leave_hruser.action_approve() @@ -446,7 +737,7 @@ class TestMultiCompany(TestHrHolidaysBase): employee_leave_hrmanager = self.employee_leave.with_user(self.user_hrmanager) with self.assertRaises(AccessError): - name = employee_leave_hrmanager.name + employee_leave_hrmanager.name with self.assertRaises(AccessError): employee_leave_hrmanager.action_approve() @@ -456,7 +747,7 @@ class TestMultiCompany(TestHrHolidaysBase): self.leave_type.write({'company_id': False}) employee_leave = self.employee_leave.with_user(self.user_employee) - name = employee_leave.name + employee_leave.name with self.assertRaises(UserError): employee_leave.action_approve() self.assertEqual(employee_leave.state, 'confirm') @@ -466,7 +757,7 @@ class TestMultiCompany(TestHrHolidaysBase): self.leave_type.write({'company_id': False}) employee_leave_hruser = self.employee_leave.with_user(self.user_hruser) - name = employee_leave_hruser.name + employee_leave_hruser.name employee_leave_hruser.action_approve() self.assertEqual(employee_leave_hruser.state, 'validate') @@ -475,6 +766,6 @@ class TestMultiCompany(TestHrHolidaysBase): self.leave_type.write({'company_id': False}) employee_leave_hrmanager = self.employee_leave.with_user(self.user_hrmanager) - name = employee_leave_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_change_department.py b/addons/hr_holidays/tests/test_change_department.py index c223b94964a..cf041d99758 100644 --- a/addons/hr_holidays/tests/test_change_department.py +++ b/addons/hr_holidays/tests/test_change_department.py @@ -4,12 +4,10 @@ from datetime import datetime from dateutil.relativedelta import relativedelta -from odoo import tests from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase class TestChangeDepartment(TestHrHolidaysBase): - def test_employee_change_department_request_change_department(self): self.HolidaysEmployeeGroup = self.env['hr.leave'].with_user(self.user_employee_id) @@ -68,4 +66,3 @@ class TestChangeDepartment(TestHrHolidaysBase): hol32_user_group.action_refuse() self.employee_emp.department_id = self.hr_dept # Change department self.assertEqual(hol32_employee_group.department_id, self.hr_dept, 'hr_holidays: refused futur leave request should change department if employee change department') - diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py index 00f8ae1c0aa..15cf8f173e8 100644 --- a/addons/hr_holidays/tests/test_holidays_flow.py +++ b/addons/hr_holidays/tests/test_holidays_flow.py @@ -189,6 +189,7 @@ class TestHolidaysFlow(TestHrHolidaysBase): self.assertEqual(hol3.state, 'confirm', 'hr_holidays: confirming should lead to confirm state') # I validate the holiday request by clicking on "To Approve" button. hol3.action_approve() + hol3.action_validate() self.assertEqual(hol3.state, 'validate', 'hr_holidays: validation should lead to validate state') # Check left days for casual leave: 19 days left _check_holidays_status(hol3_status, 20.0, 1.0, 19.0, 19.0) diff --git a/addons/hr_holidays/views/hr_holidays_views.xml b/addons/hr_holidays/views/hr_holidays_views.xml new file mode 100644 index 00000000000..d51a5404f25 --- /dev/null +++ b/addons/hr_holidays/views/hr_holidays_views.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml index 4f84c54f3fd..bce6851f157 100644 --- a/addons/hr_holidays/views/hr_leave_allocation_views.xml +++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml @@ -88,8 +88,7 @@ - +