[IMP] hr_holidays: Add specific approval for allocations

Purpose
=======

We want to allow different approvals for allocations and leaves of a same type.

e.g. In case of compensation days, a manager would approve the
allocation request, but would later not approve leave requests.

Specification
=============

Allocation modes
----------------
No change here except renames.
1. No limit (employee doesn't need an allocation to request a leave)
2. Allow employee requests
3. Fixed by Time Off Officer

Allocation approval
-------------------
A radio field to choose the approval mechanism
appears if the employee is allowed to request an allocation.
The following approval modes are possible:
1. Time Off Officer
2. Employee's Time Off Manager (leave_responsible_id)
3. Employee's Time Off Manager and Time Off Officer

These options are the same as leave approval options
(except the missing "No Validation").

If option 1 or 2 is selected, a o2m field should
allow to select which Time Off Officer is responsible
for approving those allocations.

Allocation approval policy is the same as the leave approval policy.

Task id: 1936742

--
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr

closes odoo/odoo#35446

Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Odoo's Mergebot
2019-11-25 11:56:25 +01:00
committed by GitHub
15 changed files with 398 additions and 89 deletions
+6 -3
View File
@@ -5,7 +5,8 @@
<record id="holiday_status_cl" model="hr.leave.type">
<field name="name">Paid Time Off</field>
<field name="allocation_type">fixed</field>
<field name="validation_type">both</field>
<field name="leave_validation_type">both</field>
<field name="allocation_validation_type">both</field>
<field name="color_name">black</field>
<field name="leave_notif_subtype_id" eval="ref('mt_leave')"/>
<field name="allocation_notif_subtype_id" eval="ref('mt_leave_allocation')"/>
@@ -27,7 +28,8 @@
<record id="holiday_status_comp" model="hr.leave.type">
<field name="name">Compensatory Days</field>
<field name="allocation_type">fixed_allocation</field>
<field name="validation_type">both</field>
<field name="leave_validation_type">manager</field>
<field name="allocation_validation_type">manager</field>
<field name="request_unit">hour</field>
<field name="color_name">lavender</field>
<field name="validity_start" eval="False"/>
@@ -39,7 +41,8 @@
<record id="holiday_status_unpaid" model="hr.leave.type">
<field name="name">Unpaid</field>
<field name="allocation_type">no</field>
<field name="validation_type">both</field>
<field name="leave_validation_type">both</field>
<field name="allocation_validation_type">both</field>
<field name="color_name">brown</field>
<field name="request_unit">hour</field>
<field name="unpaid" eval="True"/>
+2 -1
View File
@@ -11,7 +11,8 @@
<field name="name">Parental Time Off</field>
<field name="allocation_type">fixed</field>
<field name="color_name">brown</field>
<field name="validation_type">both</field>
<field name="leave_validation_type">both</field>
<field name="allocation_validation_type">both</field>
<field name="validity_start" eval="False"/>
<field name="responsible_id" ref="base.user_admin"/>
</record>
+17 -13
View File
@@ -77,7 +77,7 @@ class HolidaysRequest(models.Model):
lt = LeaveType.search([('valid', '=', True)], limit=1)
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'
defaults['state'] = 'confirm' if lt and lt.leave_validation_type != 'no_validation' else 'draft'
return defaults
def _default_employee(self):
@@ -126,7 +126,7 @@ class HolidaysRequest(models.Model):
"hr.leave.type", string="Time Off Type", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
domain=[('valid', '=', True)])
validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type', readonly=False)
validation_type = fields.Selection('Validation Type', related='holiday_status_id.leave_validation_type', readonly=False)
# HR data
employee_id = fields.Many2one(
'hr.employee', string='Employee', index=True, readonly=True, ondelete="restrict",
@@ -501,7 +501,7 @@ class HolidaysRequest(models.Model):
def _compute_can_approve(self):
for holiday in self:
try:
if holiday.state == 'confirm' and holiday.holiday_status_id.validation_type == 'both':
if holiday.state == 'confirm' and holiday.validation_type == 'both':
holiday._check_approval_update('validate1')
else:
holiday._check_approval_update('validate')
@@ -637,11 +637,11 @@ class HolidaysRequest(models.Model):
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
# Handle no_validation
if leave_type.validation_type == 'no_validation':
if leave_type.leave_validation_type == 'no_validation':
values.update({'state': 'confirm'})
# Handle double validation
if leave_type.validation_type == 'both':
if leave_type.leave_validation_type == 'both':
self._check_double_validation_rules(employee_id, values.get('state', False))
holiday = super(HolidaysRequest, self.with_context(mail_create_nosubscribe=True)).create(values)
@@ -662,7 +662,7 @@ class HolidaysRequest(models.Model):
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':
if holiday.validation_type == 'no_validation':
# 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])
@@ -924,16 +924,16 @@ class HolidaysRequest(models.Model):
is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager')
for holiday in self:
val_type = holiday.holiday_status_id.validation_type
val_type = holiday.validation_type
if not is_manager and state != 'confirm':
if state == 'draft':
if holiday.state == 'refuse':
raise UserError(_('Only a Leave Manager can reset a refused leave.'))
raise UserError(_('Only a Time Off Manager can reset a refused leave.'))
if holiday.date_from.date() <= fields.Date.today():
raise UserError(_('Only a Leave Manager can reset a started leave.'))
raise UserError(_('Only a Time Off Manager can reset a started leave.'))
if holiday.employee_id != current_employee:
raise UserError(_('Only a Leave Manager can reset other people leaves.'))
raise UserError(_('Only a Time Off Manager can reset other people leaves.'))
else:
if val_type == 'no_validation' and current_employee == holiday.employee_id:
continue
@@ -942,11 +942,11 @@ class HolidaysRequest(models.Model):
# This handles states validate1 validate and refuse
if holiday.employee_id == current_employee:
raise UserError(_('Only a Leave Manager can approve/refuse its own requests.'))
raise UserError(_('Only a Time Off Manager can approve/refuse its own requests.'))
if (state == 'validate1' and val_type == 'both') or (state == 'validate' and val_type == 'manager') and holiday.holiday_type == 'employee':
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))
raise UserError(_('You must be either %s\'s manager or Time off Manager to approve this leave') % (holiday.employee_id.name))
# ------------------------------------------------------------
# Activity methods
@@ -954,7 +954,11 @@ class HolidaysRequest(models.Model):
def _get_responsible_for_approval(self):
self.ensure_one()
responsible = self.env['res.users'].browse(SUPERUSER_ID)
responsible = self.env.user
if self.holiday_type != 'employee':
return responsible
if self.validation_type == 'manager' or (self.validation_type == 'both' and self.state == 'confirm'):
if self.employee_id.leave_manager_id:
@@ -13,6 +13,7 @@ from odoo.addons.resource.models.resource import HOURS_PER_DAY
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.translate import _
from odoo.tools.float_utils import float_round
from odoo.osv import expression
_logger = logging.getLogger(__name__)
@@ -40,7 +41,8 @@ class HolidaysAllocation(models.Model):
return [('valid', '=', True), ('allocation_type', '!=', 'no')]
return [('valid', '=', True), ('allocation_type', '=', 'fixed_allocation')]
name = fields.Char('Description')
name = fields.Char('Description', compute='_compute_description', inverse='_inverse_description', search='_search_description', compute_sudo=False)
private_name = fields.Char('Allocation Description', groups='hr_holidays.group_hr_holidays_user')
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'),
@@ -86,11 +88,11 @@ class HolidaysAllocation(models.Model):
linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests')
first_approver_id = fields.Many2one(
'hr.employee', string='First Approval', readonly=True, copy=False,
help='This area is automatically filled by the user who validate the allocation')
help='This area is automatically filled by the user who validates the allocation')
second_approver_id = fields.Many2one(
'hr.employee', string='Second Approval', readonly=True, copy=False,
help='This area is automatically filled by the user who validate the allocation with second level (If allocation type need second validation)')
validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type', readonly=True)
help='This area is automaticly filled by the user who validates the allocation with second level (If allocation type need second validation)')
validation_type = fields.Selection('Validation Type', related='holiday_status_id.allocation_validation_type', readonly=True)
can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
@@ -208,6 +210,35 @@ class HolidaysAllocation(models.Model):
holiday.write(values)
@api.depends_context('uid')
def _compute_description(self):
self.check_access_rights('read')
self.check_access_rule('read')
is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
for allocation in self:
if is_officer or allocation.employee_id.user_id == self.env.user or allocation.manager_id == self.env.user:
allocation.name = allocation.sudo().private_name
else:
allocation.name = '*****'
def _inverse_description(self):
is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
for allocation in self:
if is_officer or allocation.employee_id.user_id == self.env.user or allocation.manager_id == self.env.user:
allocation.sudo().private_name = allocation.name
def _search_description(self, operator, value):
is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
domain = [('private_name', operator, value)]
if not is_officer:
domain = expression.AND([domain, [('employee_id.user_id', '=', self.env.user.id)]])
allocations = self.sudo().search(domain)
return [('id', 'in', allocations.ids)]
@api.depends('employee_id', 'holiday_status_id')
def _compute_leaves(self):
for allocation in self:
@@ -251,7 +282,7 @@ class HolidaysAllocation(models.Model):
def _compute_can_approve(self):
for allocation in self:
try:
if allocation.state == 'confirm' and allocation.holiday_status_id.validation_type == 'both':
if allocation.state == 'confirm' and allocation.validation_type == 'both':
allocation._check_approval_update('validate1')
else:
allocation._check_approval_update('validate')
@@ -489,7 +520,7 @@ class HolidaysAllocation(models.Model):
).create(self._prepare_holiday_values(employee))
# TODO is it necessary to interleave the calls?
childs.action_approve()
if childs and self.holiday_status_id.validation_type == 'both':
if childs and self.validation_type == 'both':
childs.action_validate()
return childs
@@ -518,7 +549,7 @@ class HolidaysAllocation(models.Model):
is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager')
for holiday in self:
val_type = holiday.holiday_status_id.validation_type
val_type = holiday.holiday_status_id.allocation_validation_type
if state == 'confirm':
continue
@@ -541,12 +572,12 @@ class HolidaysAllocation(models.Model):
if self.env.user == holiday.employee_id.leave_manager_id and self.env.user != holiday.employee_id.user_id:
continue
manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id
if (manager and manager != current_employee) and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'):
if (manager != current_employee) and not is_manager:
raise UserError(_('You must be either %s\'s manager or time off manager to approve this time off') % (holiday.employee_id.name))
if state == 'validate' and val_type == 'both':
if not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'):
raise UserError(_('Only a Time off Manager can apply the second approval on allocation requests.'))
if not is_officer:
raise UserError(_('Only a Time off Approver can apply the second approval on allocation requests.'))
# ------------------------------------------------------------
# Activity methods
@@ -554,15 +585,14 @@ class HolidaysAllocation(models.Model):
def _get_responsible_for_approval(self):
self.ensure_one()
responsible = self.env['res.users']
responsible = self.env.user
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
if self.state == 'confirm' and self.employee_id.parent_id.user_id:
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
+15 -11
View File
@@ -70,21 +70,25 @@ class HolidaysType(models.Model):
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"
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([
leave_validation_type = fields.Selection([
('no_validation', 'No Validation'),
('hr', 'Time Off Officer'),
('manager', 'Team Leader'),
('both', 'Team Leader and Time Off Officer')], default='hr', string='Validation')
('hr', 'By Time Off Officer'),
('manager', "By Employee's Manager"),
('both', "By Employee's Manager and Time Off Officer")], default='hr', string='Leave Validation')
allocation_validation_type = fields.Selection([
('hr', 'By Time Off Officer'),
('manager', "By Employee's Manager"),
('both', "By Employee's Manager and Time Off Officer")], default='manager', string='Allocation Validation')
allocation_type = fields.Selection([
('no', 'No Allocation Needed'),
('fixed_allocation', 'Free Allocation Request'),
('fixed', 'Allocated by HR only')],
('no', 'No Limit'),
('fixed_allocation', 'Allow Employees Requests'),
('fixed', 'Fixed by Time Off Officer')],
default='no', string='Mode',
help='\tNo Allocation Needed: no allocation by default, users can freely request time off;'
'\tFree Allocation Request: allocated by HR and users can request time off and allocations;'
'\tAllocated by HR only: allocated by HR and cannot be bypassed; users can request time off;')
help='\tNo Limit: no allocation by default, users can freely request time off; '
'\tAllow Employees Requests: allocated by HR and users can request time off and allocations; '
'\tFixed by Time Off Officer: allocated by HR and cannot be bypassed; users can request time off;')
validity_start = fields.Date("From",
help='Adding validity to types of time off so that it cannot be selected outside this time period')
validity_stop = fields.Date("To")
+1 -1
View File
@@ -51,7 +51,7 @@ class LeaveReport(models.Model):
leaves.date_to as date_to, leaves.payslip_status as payslip_status
from (select
allocation.employee_id as employee_id,
allocation.name as name,
allocation.private_name as name,
allocation.number_of_days as number_of_days,
allocation.category_id as category_id,
allocation.department_id as department_id,
@@ -98,7 +98,7 @@
<field name="domain_force">[
'|',
('employee_id.leave_manager_id', '=', user.id),
('employee_id.user_id', '=', user.id)
('employee_id.user_id', '=', user.id),
]</field>
<field name="perm_create" eval="False"/>
<field name="perm_write" eval="False"/>
@@ -107,19 +107,21 @@
</record>
<record id="hr_leave_allocation_rule_employee_update" model="ir.rule">
<field name="name">Allocations: employee: create/write/unlink not validated own</field>
<field name="name">Allocations: base.group_user create/write</field>
<field name="model_id" ref="model_hr_leave_allocation"/>
<field name="domain_force">
[('holiday_type', '=', 'employee'),
<field name="domain_force">[
('holiday_status_id.allocation_type', '=', 'fixed_allocation'),
('holiday_type', '=', 'employee'),
'|',
'&amp;',
('employee_id.user_id', '=', user.id),
('state', 'not in', ['validate', 'validate1']),
'&amp;',
('validation_type', 'in', ['manager', 'both', 'no_validation']),
('employee_id.leave_manager_id', '=', user.id),]
</field>
('validation_type', 'in', ['manager', 'both']),
('employee_id.leave_manager_id', '=', user.id),
]</field>
<field name="perm_read" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
</record>
@@ -133,12 +135,29 @@
<field name="groups" eval="[(4, ref('hr_holidays.group_hr_holidays_user'))]"/>
</record>
<record id="hr_leave_allocation_rule_officer_update" model="ir.rule">
<field name="name">Allocations: see all time off: create/write/unlink own + managed employee + without department + same or managed department</field>
<record id="hr_leav_allocation_rule_employee_unlink" model="ir.rule">
<field name="name">Allocations base.group_user unlink</field>
<field name="model_id" ref="model_hr_leave_allocation"/>
<field name="domain_force">['|',
'|', ('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)]</field>
<field name="domain_force">[('employee_id.user_id', '=', user.id), ('state', '=', 'draft')]</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="hr_leave_allocation_rule_officer_update" model="ir.rule">
<field name="name">Allocations: holiday user : create/write</field>
<field name="model_id" ref="model_hr_leave_allocation"/>
<field name="domain_force">[
('holiday_type', '=', 'employee'),
'|',
'&amp;',
('employee_id.user_id', '=', user.id),
('state', '!=', 'validate'),
'|',
('employee_id.user_id', '!=', user.id),
('employee_id.user_id', '=', False)
]</field>
<field name="groups" eval="[(4,ref('hr_holidays.group_hr_holidays_user'))]"/>
</record>
+1
View File
@@ -3,6 +3,7 @@
from . import test_access_rights
from . import test_automatic_leave_dates
from . import test_allocation_access_rights
from . import test_holidays_flow
from . import test_hr_leave_type
from . import test_accrual_allocations
+10 -10
View File
@@ -16,7 +16,7 @@ class TestHrHolidaysAccessRightsCommon(TestHrHolidaysCommon):
super(TestHrHolidaysAccessRightsCommon, self).setUp()
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited',
'validation_type': 'hr',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
})
@@ -35,28 +35,28 @@ class TestHrHolidaysAccessRightsCommon(TestHrHolidaysCommon):
self.lt_no_validation = self.env['hr.leave.type'].create({
'name': 'Validation = no_validation',
'validation_type': 'hr',
'leave_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',
'leave_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',
'leave_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',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
})
@@ -670,7 +670,7 @@ class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
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.leave_type.write({'leave_validation_type': 'manager'})
values = {
'name': 'Hol HrUser',
'employee_id': self.employee_hruser_id,
@@ -688,7 +688,7 @@ class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
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.leave_type.write({'leave_validation_type': 'manager'})
values = {
'name': 'Hol HrUser',
'employee_id': self.employee_emp_id,
@@ -704,7 +704,7 @@ class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_double_validate(self):
self.leave_type.write({'validation_type': 'both'})
self.leave_type.write({'leave_validation_type': 'both'})
values = {
'name': 'double HrManager',
'employee_id': self.employee_hrmanager_id,
@@ -728,7 +728,7 @@ class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_double_validate_holiday_manager(self):
self.leave_type.write({'validation_type': 'both'})
self.leave_type.write({'leave_validation_type': 'both'})
values = {
'name': 'double HrManager',
'employee_id': self.employee_emp_id,
@@ -780,7 +780,7 @@ class TestMultiCompany(TestHrHolidaysCommon):
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited - Company New',
'company_id': self.new_company.id,
'validation_type': 'hr',
'leave_validation_type': 'hr',
'allocation_type': 'no',
})
self.rd_dept.manager_id = False
@@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import tests
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
from odoo.exceptions import AccessError, UserError
@tests.tagged('access_rights', 'post_install', '-at_install')
class TestAllocationRights(TestHrHolidaysCommon):
def setUp(self):
super().setUp()
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
self.employee_emp.parent_id = False
self.employee_emp.leave_manager_id = False
self.lt_validation_hr = self.env['hr.leave.type'].create({
'name': 'Validation = HR',
'allocation_validation_type': 'hr',
'allocation_type': 'fixed_allocation',
'validity_start': False,
})
self.lt_validation_manager = self.env['hr.leave.type'].create({
'name': 'Validation = manager',
'allocation_validation_type': 'manager',
'allocation_type': 'fixed_allocation',
'validity_start': False,
})
self.lt_validation_both = self.env['hr.leave.type'].create({
'name': 'Validation = both',
'allocation_validation_type': 'both',
'allocation_type': 'fixed_allocation',
'validity_start': False,
})
def request_allocation(self, user, values={}):
values = dict(values, **{
'name': 'Allocation',
'number_of_days': 1,
})
return self.env['hr.leave.allocation'].with_user(user).create(values)
class TestAccessRightsSimpleUser(TestAllocationRights):
def test_simple_user_request_allocation(self):
""" A simple user can request an allocation but not approve it """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
def test_simple_user_request_fixed_allocation(self):
""" A simple user cannot request an allocation if fixed by HR """
self.lt_validation_hr.allocation_type = 'fixed'
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
}
with self.assertRaises(AccessError):
self.request_allocation(self.user_employee.id, values)
def test_simple_user_reset_to_draft(self):
""" A simple user can reset to draft only his own allocation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
self.assertEqual(allocation.state, 'confirm')
allocation.action_draft()
self.assertEqual(allocation.state, 'draft', "It should be reset to draft state")
class TestAccessRightsEmployeeManager(TestAllocationRights):
def setUp(self):
super().setUp()
self.managed_employee = self.env['hr.employee'].create({
'name': 'Jolly Jumper',
'leave_manager_id': self.user_employee.id,
})
def test_manager_request_allocation_other(self):
""" A manager cannot request and approve an allocation for employees he doesn't manage """
values = {
'employee_id': self.employee_hruser.id,
'holiday_status_id': self.lt_validation_manager.id,
}
with self.assertRaises(AccessError):
self.request_allocation(self.user_employee.id, values) # user is not the employee's manager
def test_manager_approve_request_allocation(self):
""" A manager can request and approve an allocation for managed employees """
values = {
'employee_id': self.managed_employee.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "The allocation should be validated")
def test_manager_refuse_request_allocation(self):
""" A manager can request and refuse an allocation for managed employees """
values = {
'employee_id': self.managed_employee.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
allocation.action_refuse()
self.assertEqual(allocation.state, 'refuse', "The allocation should be validated")
def test_manager_batch_allocation(self):
""" A manager cannot create batch allocation """
values = {
'holiday_status_id': self.lt_validation_manager.id,
'holiday_type': 'company',
'mode_company_id': self.user_employee.company_id.id,
}
with self.assertRaises(AccessError):
self.request_allocation(self.user_employee.id, values)
def test_manager_approve_own(self):
""" A manager cannot approve his own allocation """
values = {
'employee_id': self.user_employee.employee_id.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
def test_manager_only_first_approval(self):
""" A manager can only do the first approval """
values = {
'employee_id': self.managed_employee.id,
'holiday_status_id': self.lt_validation_both.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
allocation.action_approve()
with self.assertRaises(UserError):
allocation.action_validate()
class TestAccessRightsHolidayUser(TestAllocationRights):
def test_holiday_user_request_allocation(self):
""" A holiday user can request and approve an allocation for any employee """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_user_request_fixed_allocation(self):
""" A holiday user can request and approve an allocation if fixed by HR """
self.lt_validation_hr.allocation_type = 'fixed'
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_user_both_second_approval(self):
""" A holiday user can only do the second approval when double validation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_both.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
allocation.sudo().action_approve() # First approval by someone else
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_user_batch_allocation(self):
""" A holiday user cannot create a batch allocation """
values = {
'holiday_status_id': self.lt_validation_hr.id,
'holiday_type': 'company',
'mode_company_id': self.user_employee.company_id.id,
}
with self.assertRaises(AccessError):
self.request_allocation(self.user_hruser.id, values)
def test_holiday_user_cannot_approve_own(self):
""" A holiday user cannot approve his own allocation """
values = {
'employee_id': self.employee_hruser.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
class TestAccessRightsHolidayManager(TestAllocationRights):
def test_holiday_manager_can_approve_own(self):
""" A holiday manager can approve his own allocation """
values = {
'employee_id': self.employee_hrmanager.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_hrmanager.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_manager_both_validation(self):
""" A holiday manager can perform both validation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_both.id,
}
allocation = self.request_allocation(self.user_hrmanager.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate1', "It should have been validated one time")
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "It should have been completely validated")
def test_holiday_manager_refuse_validated(self):
""" A holiday manager can refuse a validated allocation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
}
allocation = self.request_allocation(self.user_hrmanager.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
allocation.action_refuse()
self.assertEqual(allocation.state, 'refuse', "It should have been refused")
@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
from datetime import date, datetime
from odoo.tests.common import Form, tagged
from odoo.tests.common import Form
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
@tagged('prout')
class TestAutomaticLeaveDates(TestHrHolidaysCommon):
def setUp(self):
super(TestAutomaticLeaveDates, self).setUp()
@@ -15,7 +15,6 @@ class TestChangeDepartment(TestHrHolidaysCommon):
self.holidays_status_1 = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
'validity_start': False,
})
@@ -30,13 +30,13 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
self.holidays_status_hr = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
'leave_validation_type': 'hr',
'validity_start': False,
})
self.holidays_status_manager = HolidayStatusManagerGroup.create({
'name': 'NotLimitedManager',
'allocation_type': 'no',
'validation_type': 'manager',
'leave_validation_type': 'manager',
'validity_start': False,
})
@@ -87,7 +87,7 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
holiday_status_paid_time_off = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'allocation_type': 'fixed',
'validation_type': 'both',
'leave_validation_type': 'both',
'validity_start': time.strftime('%Y-%m-01'),
'responsible_id': self.env.ref('base.user_admin').id,
})
@@ -129,7 +129,8 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
self.holidays_status_limited = HolidayStatusManagerGroup.create({
'name': 'Limited',
'allocation_type': 'fixed',
'validation_type': 'both',
'allocation_validation_type': 'both',
'leave_validation_type': 'both',
'validity_start': False,
})
HolidaysEmployeeGroup = Requests.with_user(self.user_employee_id)
@@ -243,7 +244,7 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
holiday_status_paid_time_off = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'allocation_type': 'fixed',
'validation_type': 'both',
'leave_validation_type': 'both',
'validity_start': time.strftime('%Y-%m-01'),
'responsible_id': self.env.ref('base.user_admin').id,
})
@@ -31,19 +31,19 @@ class TestLeaveRequests(TestHrHolidaysCommon):
self.holidays_type_1 = LeaveType.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
'leave_validation_type': 'hr',
'validity_start': False,
})
self.holidays_type_2 = LeaveType.create({
'name': 'Limited',
'allocation_type': 'fixed',
'validation_type': 'hr',
'allocation_type': 'fixed_allocation',
'leave_validation_type': 'hr',
'validity_start': False,
})
self.holidays_type_3 = LeaveType.create({
'name': 'TimeNotLimited',
'allocation_type': 'no',
'validation_type': 'manager',
'leave_validation_type': 'manager',
'validity_start': fields.Datetime.from_string('2017-01-01 00:00:00'),
'validity_stop': fields.Datetime.from_string('2017-06-01 00:00:00'),
})
@@ -174,8 +174,8 @@ class TestLeaveRequests(TestHrHolidaysCommon):
""" Create an allocation request """
# employee should be set to current user
allocation_form = Form(self.env['hr.leave.allocation'].with_user(self.user_employee))
allocation_form.holiday_status_id = self.holidays_type_1
allocation_form.name = 'New Allocation Request'
allocation_form.holiday_status_id = self.holidays_type_2
allocation = allocation_form.save()
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@@ -22,7 +22,7 @@
<form string="Time Off Type">
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object" name="action_see_days_allocated" icon="fa-archive">
<button class="oe_stat_button" type="object" name="action_see_days_allocated" icon="fa-archive" attrs="{'invisible': [('allocation_type', '=', 'no')]}">
<div class="o_stat_info">
<field name="group_days_allocation"/>
<span class="o_stat_text">Group Days Allocated</span>
@@ -45,17 +45,21 @@
<field name="code" string="Payroll Code"/>
<field name="request_unit" widget="radio"/>
</group>
<group name="validation" string="Validation">
<field name="validation_type" widget="radio"/>
<field name="responsible_id" attrs="{'invisible': [('validation_type', 'in', ['no_validation', 'manager'])], 'required': [('validation_type', 'in', ['hr', 'both'])]}"/>
</group>
<group name="visibility" string="Allocation">
<field name="allocation_type" widget="radio" force_save="1"/>
</group>
<group name="validity" string="Validity">
<field name="validity_start"/>
<field name="validity_stop"/>
</group>
<group name="allocation_validation" string="Allocation Requests">
<field name="allocation_type" widget="radio" force_save="1"/>
<field name="allocation_validation_type" string="Approval" widget="radio" attrs="{'invisible': [('allocation_type', '!=', 'fixed_allocation')]}"/>
<field name="responsible_id"
attrs="{
'invisible': [('leave_validation_type', 'in', ['no_validation', 'manager']), '|', ('allocation_type', '=', 'no'), ('allocation_validation_type', '=', 'manager')],
'required': ['|', ('leave_validation_type', 'in', ['hr', 'both']), '&amp;', ('allocation_type', 'in', ['fixed_allocation', 'fixed']), ('allocation_validation_type', 'in', ['hr', 'both'])]}"/>
</group>
<group name="leave_validation" string="Time Off Requests">
<field name="leave_validation_type" string="Approval" widget="radio"/>
</group>
<group name="notification" string="Notification" colspan="2" groups="base.group_no_one">
<group>
<field name="leave_notif_subtype_id"
@@ -107,7 +111,8 @@
<field name="display_name"/>
<field name="code"/>
<field name="allocation_type"/>
<field name="validation_type"/>
<field name="leave_validation_type"/>
<field name="allocation_validation_type"/>
<field name="validity_start"/>
<field name="validity_stop"/>
<field name="company_id" groups="base.group_multi_company"/>