[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:
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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'),
|
||||
'|',
|
||||
'&',
|
||||
('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),]
|
||||
</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'),
|
||||
'|',
|
||||
'&',
|
||||
('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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']), '&', ('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"/>
|
||||
|
||||
Reference in New Issue
Block a user