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 @@ - +