[MERGE][REF] hr_holidays: refactor the configuration and request of leave and allocation

Purpose of this merge is to clean and simplify leave configuration and daily
use by

 * simplifying leave type configuration;
 * simplifying leave request and allocation encoding;
 * reorganizing menus to separate personal from manager menus;
 * adding missing kanban views;

For more details see sub commits. Each of them comes with its own
specifications and explanations.

This merge is linked to task ID 1876795 and closes PR #26571 .
This commit is contained in:
Thibault Delavallée
2018-09-10 13:10:13 +02:00
25 changed files with 1261 additions and 1015 deletions
+6 -3
View File
@@ -229,16 +229,17 @@ class Employee(models.Model):
if self.user_id:
self.update(self._sync_user(self.user_id))
@api.onchange('user_id', 'resource_calendar_id')
@api.onchange('resource_calendar_id')
def _onchange_timezone(self):
if self.user_id or self.resource_calendar_id:
self.tz = self.user_id.tz or self.resource_calendar_id.tz
if self.resource_calendar_id and not self.tz:
self.tz = self.resource_calendar_id.tz
def _sync_user(self, user):
return dict(
name=user.name,
image=user.image,
work_email=user.email,
tz=user.tz,
)
@api.model
@@ -254,6 +255,8 @@ class Employee(models.Model):
account_id = vals.get('bank_account_id') or self.bank_account_id.id
if account_id:
self.env['res.partner.bank'].browse(account_id).partner_id = vals['address_home_id']
if vals.get('user_id'):
vals.update(self._sync_user(self.env['res.users'].browse(vals['user_id'])))
tools.image_resize_images(vals)
return super(Employee, self).write(vals)
+6 -8
View File
@@ -2,11 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields
from odoo import SUPERUSER_ID
class User(models.Model):
_inherit = ['res.users']
employee_ids = fields.One2many('hr.employee', 'user_id', string='Related employees')
@@ -19,11 +17,11 @@ class User(models.Model):
@api.multi
def write(self, vals):
""" When renaming admin user, we want its new name propagated to its related employees """
""" Synchronize user and its related employee """
result = super(User, self).write(vals)
Employee = self.env['hr.employee']
if vals.get('name'):
for user in self.filtered(lambda user: user.id == SUPERUSER_ID):
employees = Employee.search([('user_id', '=', user.id)])
employees.write({'name': vals['name']})
employee_values = {}
for fname in [f for f in ['name', 'email', 'image', 'tz'] if f in vals]:
employee_values[fname] = vals[fname]
if employee_values:
self.env['hr.employee'].sudo().search([('user_id', 'in', self.ids)]).write(employee_values)
return result
+9 -5
View File
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<data noupdate="0">
<!-- Casual leave -->
<record id="holiday_status_cl" model="hr.leave.type">
<field name="name" eval="'Legal Leaves '+time.strftime('%Y')"/>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed</field>
<field name="color_name">black</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
@@ -14,14 +13,16 @@
<!-- Sick leave -->
<record id="holiday_status_sl" model="hr.leave.type">
<field name="name">Sick Leaves</field>
<field name="limit" eval="True"/>
<field name="allocation_type">no</field>
<field name="color_name">red</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
</record>
<!-- Compensatory Days -->
<record id="holiday_status_comp" model="hr.leave.type">
<field name="name">Compensatory Days</field>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed_allocation</field>
<field name="request_unit">hour</field>
<field name="color_name">lavender</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
@@ -30,8 +31,11 @@
<!--Unpaid Leave -->
<record id="holiday_status_unpaid" model="hr.leave.type">
<field name="name">Unpaid</field>
<field name="allocation_type">fixed_allocation</field>
<field name="validation_type">both</field>
<field name="color_name">brown</field>
<field name="unpaid" eval="True"/>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
</record>
</data>
</odoo>
+9 -11
View File
@@ -4,7 +4,7 @@
<record id="hr_holiday_status_hl" model="hr.leave.type">
<field name="name">Overtime Compensation</field>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed_allocation</field>
<field name="color_name">blue</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
@@ -13,24 +13,23 @@
<record id="hr_holiday_status_dv" model="hr.leave.type">
<field name="name">Parental Leaves</field>
<field name="limit" eval="True"/>
<field name="allocation_type">fixed</field>
<field name="color_name">brown</field>
<field name="validation_type">both</field>
</record>
<record id="holiday_status_cla" model="hr.leave.type">
<field name="name" eval="'Paid Time Off ' + time.strftime('%Y')"/>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed</field>
<field name="color_name">wheat</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
<field name="accrual" eval="True"/>
</record>
<record id="hr_holidays_employee1_allocation_cl" model="hr.leave.allocation">
<field name="name">Legal Leaves for Peter Parker</field>
<field name="holiday_status_id" ref="holiday_status_cl"/>
<field name="number_of_days_temp">20</field>
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">confirm</field>
</record>
@@ -38,7 +37,7 @@
<record id="hr_holidays_employee1_int_tour" model="hr.leave.allocation">
<field name="name">International Tour</field>
<field name="holiday_status_id" ref="holiday_status_comp"/>
<field name="number_of_days_temp">7</field>
<field name="number_of_days">7</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">confirm</field>
</record>
@@ -46,15 +45,14 @@
<record id="hr_holidays_employee1_allocation_hl" model="hr.leave.allocation">
<field name="name">Overtime Compensation</field>
<field name="holiday_status_id" ref="hr_holiday_status_hl"/>
<field name="number_of_hours">160</field>
<field name="number_of_days_temp">20</field>
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_admin"/>
</record>
<record id="hr_holidays_employee1_vc" model="hr.leave.allocation">
<field name="name">Summer Vacation</field>
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days_temp">7</field>
<field name="number_of_days">7</field>
<field name="employee_id" ref="hr.employee_admin"/>
</record>
@@ -83,7 +81,7 @@
<field name="holiday_status_id" ref="holiday_status_comp"/>
<field eval="time.strftime('%Y-%m-01')" name="date_from"/>
<field eval="time.strftime('%Y-%m-03')" name="date_to"/>
<field name="number_of_days_temp">3</field>
<field name="number_of_days">3</field>
<field name="employee_id" ref="hr.employee_admin"/>
</record>
@@ -92,7 +90,7 @@
<field name="holiday_status_id" ref="holiday_status_sl"/>
<field eval="time.strftime('%Y-%m-20')" name="date_from"/>
<field eval="time.strftime('%Y-%m-22')" name="date_to"/>
<field name="number_of_days_temp">3</field>
<field name="number_of_days">3</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">confirm</field>
</record>
+4 -38
View File
@@ -61,10 +61,10 @@ class Department(models.Model):
class Employee(models.Model):
_inherit = "hr.employee"
remaining_leaves = fields.Float(compute='_compute_remaining_leaves', string='Remaining Legal Leaves', inverse='_inverse_remaining_leaves',
remaining_leaves = fields.Float(
compute='_compute_remaining_leaves', string='Remaining Legal Leaves',
help='Total number of legal leaves allocated to this employee, change this value to create allocation/leave request. '
'Total based on all the leave types without overriding limit.')
current_leave_state = fields.Selection(compute='_compute_leave_status', string="Current Leave Status",
@@ -97,14 +97,14 @@ class Employee(models.Model):
state, employee_id
FROM hr_leave_allocation
UNION
SELECT holiday_status_id, number_of_days,
SELECT holiday_status_id, (number_of_days * -1) as number_of_days,
state, employee_id
FROM hr_leave
) h
join hr_leave_type s ON (s.id=h.holiday_status_id)
WHERE
h.state='validate' AND
s.limit=False AND
(s.allocation_type='fixed' OR s.allocation_type='fixed_allocation') AND
h.employee_id in %s
GROUP BY h.employee_id""", (tuple(self.ids),))
return dict((row['employee_id'], row['days']) for row in self._cr.dictfetchall())
@@ -115,39 +115,6 @@ class Employee(models.Model):
for employee in self:
employee.remaining_leaves = float_round(remaining.get(employee.id, 0.0), precision_digits=2)
@api.multi
def _inverse_remaining_leaves(self):
status_list = self.env['hr.leave.type'].search([('limit', '=', False)])
# Create leaves (adding remaining leaves) or raise (reducing remaining leaves)
actual_remaining = self._get_remaining_leaves()
for employee in self.filtered(lambda employee: employee.remaining_leaves):
# check the status list. This is done here and not before the loop to avoid raising
# exception on employee creation (since we are in a computed field).
if len(status_list) != 1:
raise UserError(_("The feature behind the field 'Remaining Legal Leaves' can only be used when there is only one "
"leave type with the option 'Allow to Override Limit' unchecked. (%s Found). "
"Otherwise, the update is ambiguous as we cannot decide on which leave type the update has to be done. "
"\n You may prefer to use the classic menus 'Leave Requests' and 'Allocation Requests' located in Leaves Application "
"to manage the leave days of the employees if the configuration does not allow to use this field.") % (len(status_list)))
status = status_list[0] if status_list else None
if not status:
continue
# if a status is found, then compute remaing leave for current employee
difference = float_round(employee.remaining_leaves - actual_remaining.get(employee.id, 0), precision_digits=2)
if difference > 0:
leave = self.env['hr.leave.allocation'].create({
'name': _('Allocation for %s') % employee.name,
'employee_id': employee.id,
'holiday_status_id': status.id,
'holiday_type': 'employee',
'number_of_days_temp': difference
})
leave.action_approve()
if leave.validation_type == 'both':
leave.action_validate()
elif difference < 0:
raise UserError(_('You cannot reduce validated allocation requests.'))
@api.multi
def _compute_leave_status(self):
# Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule
@@ -175,7 +142,6 @@ class Employee(models.Model):
def _compute_leaves_count(self):
all_leaves = self.env['hr.leave.report'].read_group([
('employee_id', 'in', self.ids),
('holiday_status_id.limit', '=', False),
('state', '=', 'validate')
], fields=['number_of_days', 'employee_id'], groupby=['employee_id'])
mapping = dict([(leave['employee_id'][0], leave['number_of_days']) for leave in all_leaves])
+249 -185
View File
@@ -6,16 +6,15 @@
import logging
import math
from datetime import timedelta, datetime, time
from datetime import datetime
from pytz import timezone, UTC
from odoo import api, fields, models
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
from odoo.tools.translate import _
from odoo.addons.resource.models.resource import float_to_time
_logger = logging.getLogger(__name__)
@@ -60,17 +59,13 @@ class HolidaysRequest(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_mail_post_access = 'read'
def _default_domain_holiday_status(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_manager'):
return [('valid', '=', True)]
return [('valid', '=', True), ('employee_applicability', 'in', ['leave', 'both'])]
@api.model
def default_get(self, fields_list):
defaults = super(HolidaysRequest, self).default_get(fields_list)
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(self._default_domain_holiday_status())
lt = LeaveType.search([('valid', '=', True)])
defaults['holiday_status_id'] = lt[0].id if len(lt) > 0 else defaults.get('holiday_status_id')
return defaults
@@ -78,6 +73,26 @@ class HolidaysRequest(models.Model):
def _default_employee(self):
return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
def _default_get_request_parameters(self, values):
new_values = dict(values)
global_from, global_to = False, False
# TDE FIXME: consider a mapping on several days that is not the standard
# calendar widget 7-19 in user's TZ is some custom input
if values.get('date_from'):
user_tz = self.env.user.tz or 'UTC'
localized_dt = timezone('UTC').localize(values['date_from']).astimezone(timezone(user_tz))
global_from = localized_dt.time().hour == 7 and localized_dt.time().minute == 0
new_values['request_date_from'] = values['date_from'].date()
if values.get('date_to'):
user_tz = self.env.user.tz or 'UTC'
localized_dt = timezone('UTC').localize(values['date_to']).astimezone(timezone(user_tz))
global_to = localized_dt.time().hour == 19 and localized_dt.time().minute == 0
new_values['request_date_to'] = values['date_to'].date()
if global_from and global_to:
new_values['request_unit_custom'] = True
return new_values
# description
name = fields.Char('Description')
state = fields.Selection([
('draft', 'To Submit'),
@@ -87,233 +102,261 @@ class HolidaysRequest(models.Model):
('validate1', 'Second Approval'),
('validate', 'Approved')
], string='Status', readonly=True, track_visibility='onchange', copy=False, default='confirm',
help="The status is set to 'To Submit', when a leave request is created." +
"\nThe status is 'To Approve', when leave request is confirmed by user." +
"\nThe status is 'Refused', when leave request is refused by manager." +
"\nThe status is 'Approved', when leave request is approved by manager.")
payslip_status = fields.Boolean('Reported in last payslips',
help='Green this button when the leave has been taken into account in the payslip.')
help="The status is set to 'To Submit', when a leave request is created." +
"\nThe status is 'To Approve', when leave request is confirmed by user." +
"\nThe status is 'Refused', when leave request is refused by manager." +
"\nThe status is 'Approved', when leave request is approved by manager.")
payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the leave 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, store=True, default=lambda self: self.env.uid, readonly=True)
date_from = fields.Datetime('Start Date', readonly=True, index=True, copy=False, required=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
date_to = fields.Datetime('End Date', readonly=True, copy=False, required=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
holiday_status_id = fields.Many2one("hr.leave.type", string="Leave Type", required=True, readonly=True,
# leave type configuration
holiday_status_id = fields.Many2one(
"hr.leave.type", string="Leave Type", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
domain=lambda self: self._default_domain_holiday_status())
employee_id = fields.Many2one('hr.employee', string='Employee', index=True, readonly=True,
domain=[('valid', '=', True)])
leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type')
# HR data
employee_id = fields.Many2one(
'hr.employee', string='Employee', index=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange')
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)]})
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
number_of_days_temp = fields.Float(
'Duration (Days)', copy=False, readonly=True,
# duration
date_from = fields.Datetime(
'Start Date', readonly=True, index=True, copy=False, required=True,
default=fields.Datetime.now,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
date_to = fields.Datetime(
'End Date', readonly=True, copy=False, required=True,
default=fields.Datetime.now,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
number_of_days = fields.Float(
'Duration (Days)', copy=False, readonly=True, track_visibility='onchange',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help='Number of days of the leave request according to your working schedule.')
number_of_days = fields.Float('Number of Days', compute='_compute_number_of_days', store=True, track_visibility='onchange')
number_of_hours = fields.Float(
'Duration (Hours)', copy=False, readonly=True, compute='_compute_number_of_hours',
help='Number of hours of the leave request according to your working schedule.')
number_of_days_display = fields.Float(
'Duration in days', compute='_compute_number_of_days_display', copy=False, readonly=True,
help='Number of days of the leave request. Used for interface.')
number_of_hours_display = fields.Float(
'Duration in hours', compute='_compute_number_of_hours_display', copy=False, readonly=True,
help='Number of hours of the leave request according to your working schedule. Used for interface.')
# details
meeting_id = fields.Many2one('calendar.event', string='Meeting')
parent_id = fields.Many2one('hr.leave', string='Parent', copy=False)
linked_request_ids = fields.One2many('hr.leave', 'parent_id', string='Linked Requests')
department_id = fields.Many2one('hr.department', string='Department', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
holiday_type = fields.Selection([
('employee', 'By Employee'),
('company', 'By Company'),
('department', 'By Department'),
('category', 'By Employee Tag')
], string='Allocation Mode', readonly=True, required=True, default='employee',
('category', 'By Employee Tag')],
string='Allocation Mode', readonly=True, required=True, default='employee',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category')
first_approver_id = fields.Many2one('hr.employee', string='First Approval', readonly=True, copy=False,
category_id = fields.Many2one(
'hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
mode_company_id = fields.Many2one(
'res.company', string='Company', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
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 leave', oldname='manager_id')
second_approver_id = fields.Many2one('hr.employee', string='Second Approval', readonly=True, copy=False, oldname='manager_id2',
second_approver_id = fields.Many2one(
'hr.employee', string='Second Approval', readonly=True, copy=False, oldname='manager_id2',
help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)')
validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type')
can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
# Those fields are mostly for the interface only
# UX fields
leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
# Interface fields used when not using hour-based computation
request_date_from = fields.Date('Request Start Date')
request_date_to = fields.Date('Request End Date')
leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
# These fields are only used only when the leave is taken in half days
request_date_from_period = fields.Selection([('am', 'Morning'),
('pm', 'Afternoon')], string="Date Period Start", default='am')
request_date_to_period = fields.Selection([('am', 'Morning'),
('pm', 'Afternoon')], string="Date Period End", default='pm')
request_unit_all = fields.Selection([('half', 'Half-day'),
('day', '1 Day'),
('period', 'Period')])
# Duplicate field because we cannot hide some entries of a selection field
request_unit_day = fields.Selection([('day', '1 Day'),
('period', 'Period')], default='day')
# Interface fields used when using hour-based computation
request_hour_from = fields.Selection([
(0, '12:00 PM'), (0.5, '0:30 AM'),
(1, '1:00 AM'), (1.5, '1:30 AM'),
(2, '2:00 AM'), (2.5, '2:30 AM'),
(3, '3:00 AM'), (3.5, '3:30 AM'),
(4, '4:00 AM'), (4.5, '4:30 AM'),
(5, '5:00 AM'), (5.5, '5:30 AM'),
(6, '6:00 AM'), (6.5, '6:30 AM'),
(7, '7:00 AM'), (7.5, '7:30 AM'),
(8, '8:00 AM'), (8.5, '8:30 AM'),
(9, '9:00 AM'), (9.5, '9:30 AM'),
(10, '10:00 AM'), (10.5, '10:30 AM'),
(11, '11:00 AM'), (11.5, '11:30 AM'),
(12, '12:00 AM'), (12.5, '0:30 PM'),
(13, '1:00 PM'), (13.5, '1:30 PM'),
(14, '2:00 PM'), (14.5, '2:30 PM'),
(15, '3:00 PM'), (15.5, '3:30 PM'),
(16, '4:00 PM'), (16.5, '4:30 PM'),
(17, '5:00 PM'), (17.5, '5:30 PM'),
(18, '6:00 PM'), (18.5, '6:30 PM'),
(19, '7:00 PM'), (19.5, '7:30 PM'),
(20, '8:00 PM'), (20.5, '8:30 PM'),
(21, '9:00 PM'), (21.5, '9:30 PM'),
(22, '10:00 PM'), (22.5, '10:30 PM'),
(23, '11:00 PM'), (23.5, '11:30 PM')], string='Hour from')
request_hour_to = fields.Selection([
(0, '12:00 PM'), (0.5, '0:30 AM'),
(1, '1:00 AM'), (1.5, '1:30 AM'),
(2, '2:00 AM'), (2.5, '2:30 AM'),
(3, '3:00 AM'), (3.5, '3:30 AM'),
(4, '4:00 AM'), (4.5, '4:30 AM'),
(5, '5:00 AM'), (5.5, '5:30 AM'),
(6, '6:00 AM'), (6.5, '6:30 AM'),
(7, '7:00 AM'), (7.5, '7:30 AM'),
(8, '8:00 AM'), (8.5, '8:30 AM'),
(9, '9:00 AM'), (9.5, '9:30 AM'),
(10, '10:00 AM'), (10.5, '10:30 AM'),
(11, '11:00 AM'), (11.5, '11:30 AM'),
(12, '12:00 AM'), (12.5, '0:30 PM'),
(13, '1:00 PM'), (13.5, '1:30 PM'),
(14, '2:00 PM'), (14.5, '2:30 PM'),
(15, '3:00 PM'), (15.5, '3:30 PM'),
(16, '4:00 PM'), (16.5, '4:30 PM'),
(17, '5:00 PM'), (17.5, '5:30 PM'),
(18, '6:00 PM'), (18.5, '6:30 PM'),
(19, '7:00 PM'), (19.5, '7:30 PM'),
(20, '8:00 PM'), (20.5, '8:30 PM'),
(21, '9:00 PM'), (21.5, '9:30 PM'),
(22, '10:00 PM'), (22.5, '10:30 PM'),
(23, '11:00 PM'), (23.5, '11:30 PM')], string='Hour to')
# used only when the leave is taken in half days
request_date_from_period = fields.Selection([
('am', 'Morning'), ('pm', 'Afternoon')],
string="Date Period Start", default='am')
# request type
request_unit_half = fields.Boolean('Half Day')
request_unit_hours = fields.Boolean('Custom Hours')
request_unit_custom = fields.Boolean('Days-long custom hours')
_sql_constraints = [
('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL) or (holiday_type='department' AND department_id IS NOT NULL) )",
"The employee, department or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
('type_value',
"CHECK((holiday_type='employee' AND employee_id IS NOT NULL) or "
"(holiday_type='company' AND mode_company_id IS NOT NULL) or "
"(holiday_type='category' AND category_id IS NOT NULL) or "
"(holiday_type='department' AND department_id IS NOT NULL) )",
"The employee, department, company or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
('date_check2', "CHECK ((date_from <= date_to))", "The start date must be anterior to the end date."),
('date_check', "CHECK ( number_of_days_temp >= 0 )", "If you want to change the number of days you should use the 'period' mode"),
('duration_check', "CHECK ( number_of_days >= 0 )", "If you want to change the number of days you should use the 'period' mode"),
]
@api.onchange('request_unit_all', 'request_date_from_period', 'request_date_to_period',
'holiday_status_id', 'request_date_from', 'request_date_to', 'employee_id')
@api.onchange('request_date_from_period', 'request_hour_from', 'request_hour_to',
'request_date_from', 'request_date_to',
'employee_id')
def _onchange_request_parameters(self):
date_from = False
date_to = False
if not self.employee_id:
if not self.request_date_from:
self.date_from = False
return
if self.request_date_from:
if self.date_from:
date_from = fields.Datetime.to_string(datetime.combine(fields.Date.from_string(self.request_date_from), fields.Datetime.from_string(self.date_from).time()))
else:
date_from = datetime.combine(self.request_date_from, time.min)
if self.request_unit_half or self.request_unit_hours:
self.request_date_to = self.request_date_from
if self.request_date_to:
if self.date_to:
date_to = fields.Datetime.to_string(datetime.combine(fields.Date.from_string(self.request_date_to), fields.Datetime.from_string(self.date_to).time()))
else:
date_to = datetime.combine(self.request_date_to, time.max)
if not self.request_date_from or not self.request_date_to:
if date_from:
self.date_from = date_from
if date_to:
self.date_to = date_to
self.number_of_days_temp = 0
if not self.request_date_to:
self.date_to = False
return
domain = [('calendar_id', '=', self.employee_id.resource_calendar_id.id)]
domain = [('calendar_id', '=', self.employee_id.resource_calendar_id.id or self.env.user.company_id.resource_calendar_id.id)]
attendances = self.env['resource.calendar.attendance'].search(domain, order='dayofweek, day_period DESC')
first_day = fields.Date.from_string(date_from)
last_day = fields.Date.from_string(date_to)
if self.request_unit_all in ['day', 'half']:
last_day = first_day
# find first attendance coming after first_day
attendance_from = next((att for att in attendances if int(att.dayofweek) >= first_day.weekday()), attendances[0])
attendance_from = next((att for att in attendances if int(att.dayofweek) >= self.request_date_from.weekday()), attendances[0])
# find last attendance coming before last_day
attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= last_day.weekday()), attendances[-1])
attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= self.request_date_to.weekday()), attendances[-1])
if self.request_unit_all == 'day' or (self.request_unit_all == 'period' and self.leave_type_request_unit == 'day'):
if self.request_unit_half:
if self.request_date_from_period == 'am':
hour_from = float_to_time(attendance_from.hour_from)
hour_to = float_to_time(attendance_from.hour_to)
else:
hour_from = float_to_time(attendance_to.hour_from)
hour_to = float_to_time(attendance_to.hour_to)
elif self.request_unit_hours:
hour_from = float_to_time(self.request_hour_from)
hour_to = float_to_time(self.request_hour_to)
elif self.request_unit_custom:
hour_from = self.date_from.time()
hour_to = self.date_to.time()
else:
hour_from = float_to_time(attendance_from.hour_from)
hour_to = float_to_time(attendance_to.hour_to)
elif self.request_unit_all == 'half':
hour_from = float_to_time(attendance_from.hour_from if self.request_date_from_period == 'am' else attendance_to.hour_from)
hour_to = float_to_time(attendance_from.hour_to if self.request_date_from_period == 'am' else attendance_to.hour_to)
tz = self.env.user.tz if self.env.user.tz and not self.request_unit_custom else 'UTC' # custom -> already in UTC
self.date_from = timezone(tz).localize(datetime.combine(self.request_date_from, hour_from)).astimezone(UTC).replace(tzinfo=None)
self.date_to = timezone(tz).localize(datetime.combine(self.request_date_to, hour_to)).astimezone(UTC).replace(tzinfo=None)
elif self.request_unit_all == 'period' and self.leave_type_request_unit == 'half':
hour_from = float_to_time(attendance_from.hour_from if self.request_date_from_period == 'am' else attendance_from.hour_to)
hour_to = float_to_time(attendance_to.hour_from if self.request_date_to_period == 'am' else attendance_to.hour_to)
@api.onchange('request_unit_half')
def _onchange_request_unit_half(self):
if self.request_unit_half:
self.request_unit_hours = False
self.request_unit_custom = False
self._onchange_request_parameters()
if self.leave_type_request_unit == 'hour' and self.request_unit_all == 'period':
date_from = fields.Datetime.from_string(date_from)
date_to = fields.Datetime.from_string(date_to)
else:
tz = self.env.user.tz or 'UTC'
date_from = timezone(tz).localize(datetime.combine(first_day, hour_from)).astimezone(UTC)
date_to = timezone(tz).localize(datetime.combine(last_day, hour_to)).astimezone(UTC)
@api.onchange('request_unit_hours')
def _onchange_request_unit_hours(self):
if self.request_unit_hours:
self.request_unit_half = False
self.request_unit_custom = False
self._onchange_request_parameters()
self.date_from = date_from
self.date_to = date_to
if not (self.leave_type_request_unit == 'hour' and self.request_unit_all == 'period'):
date_from = date_from
date_to = date_to
self.number_of_days_temp = self._get_number_of_days(date_from, date_to, self.employee_id.id)
@api.onchange('request_unit_day')
def _onchange_request_unit_day(self):
self.request_unit_all = self.request_unit_day
@api.onchange('request_unit_custom')
def _onchange_request_unit_custom(self):
if self.request_unit_custom:
self.request_unit_half = False
self.request_unit_hours = False
self._onchange_request_parameters()
@api.onchange('holiday_type')
def _onchange_type(self):
if self.holiday_type == 'employee' and not self.employee_id:
if self.env.user.employee_ids:
self.employee_id = self.env.user.employee_ids[0]
elif self.holiday_type == 'department':
if self.env.user.employee_ids:
self.department_id = self.department_id or self.env.user.employee_ids[0].department_id
self.employee_id = None
self.employee_id = self.env.user.employee_ids[0].id
self.mode_company_id = False
self.category_id = False
elif self.holiday_type == 'company' and not self.mode_company_id:
self.employee_id = False
self.mode_company_id = self.env.user.company_id.id
self.category_id = False
elif self.holiday_type == 'department' and not self.department_id:
self.employee_id = False
self.mode_company_id = False
self.department_id = self.env.user.employee_ids[0].department_id.id
self.category_id = False
elif self.holiday_type == 'category':
self.employee_id = None
self.department_id = None
@api.onchange('date_from')
def _onchange_date_from(self):
""" If there are no date set for date_to, automatically set one 8 hours later than
the date_from. Also update the number_of_days.
"""
date_from = fields.Datetime.from_string(self.date_from)
date_to = fields.Datetime.from_string(self.date_to)
# No date_to set so far: automatically compute one 8 hours later
if date_from and not date_to:
date_to = date_from + timedelta(hours=self.employee_id.resource_calendar_id.hours_per_day)
self.date_to = date_to
self.request_date_from = date_from.date() if date_from else False
self.request_date_to = date_to.date() if date_to else False
if (date_from and date_to) and (date_from.day < date_to.day):
self.request_unit_all = 'period'
self.request_unit_day = 'period'
# Compute and update the number of days
if (date_to and date_from) and (date_from <= date_to):
self.number_of_days_temp = self._get_number_of_days(date_from, date_to, self.employee_id.id)
else:
self.number_of_days_temp = 0
@api.onchange('date_to')
def _onchange_date_to(self):
""" Update the number_of_days. """
date_from = fields.Datetime.from_string(self.date_from)
date_to = fields.Datetime.from_string(self.date_to)
self.request_date_from = date_from.date() if date_from else False
self.request_date_to = date_to.date() if date_to else False
if (date_from and date_to) and (date_from.day < date_to.day):
self.request_unit_all = 'period'
self.request_unit_day = 'period'
# Compute and update the number of days
if (date_to and date_from) and (date_from <= date_to):
self.number_of_days_temp = self._get_number_of_days(date_from, date_to, self.employee_id.id)
else:
self.number_of_days_temp = 0
self.employee_id = False
self.mode_company_id = False
self.department_id = False
@api.onchange('employee_id')
def _onchange_employee_id(self):
self.manager_id = self.employee_id and self.employee_id.parent_id
self.department_id = self.employee_id.department_id
self.manager_id = self.employee_id.parent_id.id
if self.employee_id:
self.department_id = self.employee_id.department_id
@api.onchange('date_from', 'date_to')
def _onchange_leave_dates(self):
if self.date_from and self.date_to:
self.number_of_days = self._get_number_of_days(self.date_from, self.date_to, self.employee_id.id)
else:
self.number_of_days = 0
@api.multi
@api.depends('number_of_days_temp')
def _compute_number_of_days(self):
@api.depends('number_of_days')
def _compute_number_of_days_display(self):
for holiday in self:
holiday.number_of_days = -holiday.number_of_days_temp
holiday.number_of_days_display = holiday.number_of_days
@api.multi
@api.depends('number_of_days_temp')
def _compute_number_of_hours(self):
@api.depends('number_of_days')
def _compute_number_of_hours_display(self):
for holiday in self:
holiday.number_of_hours = holiday.number_of_days_temp * self.employee_id.resource_calendar_id.hours_per_day
calendar = self.employee_id.resource_calendar_id or self.env.user.company_id.resource_calendar_id
holiday.number_of_hours_display = holiday.number_of_days * (calendar.hours_per_day or HOURS_PER_DAY)
@api.multi
@api.depends('state', 'employee_id', 'department_id')
@@ -353,10 +396,10 @@ class HolidaysRequest(models.Model):
if nholidays:
raise ValidationError(_('You can not have 2 leaves that overlaps on the same day.'))
@api.constrains('state', 'number_of_days_temp', 'holiday_status_id')
@api.constrains('state', 'number_of_days', 'holiday_status_id')
def _check_holidays(self):
for holiday in self:
if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.limit or holiday.holiday_status_id.negative:
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 \
@@ -382,9 +425,21 @@ class HolidaysRequest(models.Model):
res = []
for leave in self:
if self.env.context.get('short_name'):
res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days_temp)))
res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days)))
else:
res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp)))
if leave.holiday_type == 'company':
target = leave.mode_company_id.name
elif leave.holiday_type == 'department':
target = leave.department_id.name
elif leave.holiday_type == 'category':
target = leave.category_id.name
else:
target = leave.employee_id.name
res.append(
(leave.id,
_("%s on %s :%.2f day(s)") %
(target, leave.holiday_status_id.name, leave.number_of_days))
)
return res
@api.multi
@@ -499,11 +554,12 @@ class HolidaysRequest(models.Model):
@api.multi
def _prepare_holidays_meeting_values(self):
self.ensure_one()
calendar = self.employee_id.resource_calendar_id or self.env.user.company_id.resource_calendar_id
meeting_values = {
'name': self.display_name,
'categ_ids': [(6, 0, [
self.holiday_status_id.categ_id.id])] if self.holiday_status_id.categ_id else [],
'duration': self.number_of_days_temp * self.employee_id.resource_calendar_id.hours_per_day,
'duration': self.number_of_days * (calendar.hours_per_day or HOURS_PER_DAY),
'description': self.notes,
'user_id': self.user_id.id,
'start': self.date_from,
@@ -528,7 +584,7 @@ class HolidaysRequest(models.Model):
'date_from': self.date_from,
'date_to': self.date_to,
'notes': self.notes,
'number_of_days_temp': self.number_of_days_temp,
'number_of_days': self.number_of_days,
'parent_id': self.id,
'employee_id': employee.id
}
@@ -586,12 +642,20 @@ class HolidaysRequest(models.Model):
holiday.write({'first_approver_id': current_employee.id})
if holiday.holiday_type == 'employee':
holiday._validate_leave_request()
elif holiday.holiday_type in ['category', 'department']:
elif holiday.holiday_type in ['company', 'category', 'department']:
leaves = self.env['hr.leave']
employees = holiday.category_id.employee_ids if holiday.holiday_type == 'category' else holiday.department_id.member_ids
if holiday.holiday_type == 'category':
employees = holiday.category_id.employee_ids
elif holiday.holiday_type == 'company':
employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)])
else:
holiday.department_id.member_ids
for employee in employees:
values = holiday._prepare_holiday_values(employee)
leaves += self.with_context(mail_notify_force_send=False).create(values)
leaves += self.with_context(
mail_notify_force_send=False,
mail_activity_automation_skip=True
).create(values)
# TODO is it necessary to interleave the calls?
leaves.action_approve()
if leaves and leaves[0].validation_type == 'both':
+136 -84
View File
@@ -5,17 +5,14 @@
import logging
import pytz
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.addons.resource.models.resource import HOURS_PER_DAY
from odoo.exceptions import AccessError, UserError
from odoo.tools.translate import _
from odoo.addons.resource.models.resource import HOURS_PER_DAY
_logger = logging.getLogger(__name__)
@@ -26,18 +23,15 @@ class HolidaysAllocation(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_mail_post_access = 'read'
def _default_domain_holiday_status_id(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_manager'):
return [('valid', '=', True), ('limit', '=', False)]
return [('valid', '=', True), ('employee_applicability', 'in', ['allocation', 'both']), ('limit', '=', False)]
def _default_employee(self):
return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
def _default_holiday_status_id(self):
LeaveType = self.env['hr.leave.type'].with_context(employee_id=self._default_employee().id)
lt = LeaveType.search(self._default_domain_holiday_status_id())
return lt[:1]
if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
domain = [('valid', '=', True)]
else:
domain = [('valid', '=', True), ('allocation_type', 'in', ('no', 'fixed_allocation'))]
return self.env['hr.leave.type'].search(domain, limit=1)
name = fields.Char('Description')
state = fields.Selection([
@@ -48,47 +42,74 @@ class HolidaysAllocation(models.Model):
('validate1', 'Second Approval'),
('validate', 'Approved')
], string='Status', readonly=True, track_visibility='onchange', copy=False, default='confirm',
help="The status is set to 'To Submit', when a leave request is created." +
"\nThe status is 'To Approve', when leave request is confirmed by user." +
"\nThe status is 'Refused', when leave request is refused by manager." +
"\nThe status is 'Approved', when leave request is approved by manager.")
date_from = fields.Datetime('Start Date', readonly=True, index=True, copy=False,
help="The status is set to 'To Submit', when a leave request is created." +
"\nThe status is 'To Approve', when leave request is confirmed by user." +
"\nThe status is 'Refused', when leave request is refused by manager." +
"\nThe status is 'Approved', when leave request is approved by manager.")
date_from = fields.Datetime(
'Start Date', readonly=True, index=True, copy=False,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
date_to = fields.Datetime('End Date', readonly=True, copy=False,
date_to = fields.Datetime(
'End Date', readonly=True, copy=False,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
holiday_status_id = fields.Many2one("hr.leave.type", string="Leave Type", required=True, readonly=True,
holiday_status_id = fields.Many2one(
"hr.leave.type", string="Leave Type", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
domain=lambda self: self._default_domain_holiday_status_id(), default=_default_holiday_status_id)
employee_id = fields.Many2one('hr.employee', string='Employee', index=True, readonly=True,
domain=[('valid', '=', True)], default=_default_holiday_status_id)
employee_id = fields.Many2one(
'hr.employee', string='Employee', index=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange')
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
number_of_days_temp = fields.Float(
'Duration (days)', copy=False, readonly=True,
# duration
number_of_days = fields.Float(
'Number of Days', track_visibility='onchange',
help='Duration in days. Reference field to use when necessary.')
number_of_days_display = fields.Float(
'Duration (days)', compute='_compute_number_of_days_display', inverse='_inverse_number_of_days_display',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help='Number of days of the leave request according to your working schedule.')
number_of_days = fields.Float('Number of Days', compute='_compute_number_of_days', store=True, track_visibility='onchange')
number_of_hours = fields.Float('Duration (hours)', help="Number of hours of the leave allocation according to your working schedule.")
help="UX field allowing to see and modify the allocation duration, computed in days.")
number_of_hours_display = fields.Float(
'Duration (hours)', compute='_compute_number_of_hours_display', inverse='_inverse_number_of_hours_display',
help="UX field allowing to see and modify the allocation duration, computed in hours.")
# details
parent_id = fields.Many2one('hr.leave.allocation', string='Parent')
linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests')
department_id = fields.Many2one('hr.department', string='Department', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
holiday_type = fields.Selection([
('employee', 'By Employee'),
('department', 'By Department'),
('category', 'By Employee Tag')
], string='Allocation Mode', readonly=True, required=True, default='employee',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help='By Employee: Allocation for individual Employee, By Employee Tag: Allocation for group of employees in category')
first_approver_id = fields.Many2one('hr.employee', string='First Approval', readonly=True, copy=False,
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 leave', oldname='manager_id')
second_approver_id = fields.Many2one('hr.employee', string='Second Approval', readonly=True, copy=False, oldname='manager_id2',
second_approver_id = fields.Many2one(
'hr.employee', string='Second Approval', readonly=True, copy=False, oldname='manager_id2',
help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)')
validation_type = fields.Selection('Validation Type', related='holiday_status_id.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)
accrual = fields.Boolean("Accrual", related='holiday_status_id.accrual', store=True, readonly=True)
# mode
holiday_type = fields.Selection([
('employee', 'By Employee'),
('company', 'By Company'),
('department', 'By Department'),
('category', 'By Employee Tag')],
string='Allocation Mode', readonly=True, required=True, default='employee',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help="Allow to create requests in batchs:\n- By Employee: for a specific employee"
"\n- By Company: all employees of the specified company"
"\n- By Department: all employees of the specified department"
"\n- By Employee Tag: all employees of the specific employee group category")
mode_company_id = fields.Many2one(
'res.company', string='Company', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
department_id = fields.Many2one(
'hr.department', string='Department', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
category_id = fields.Many2one(
'hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
# accrual configuration
accrual = fields.Boolean(
"Accrual", readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
accrual_limit = fields.Integer('Balance limit', default=0, help="Maximum of allocation for accrual; 0 means no maximum.")
number_per_interval = fields.Float("Number of unit per interval", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
interval_number = fields.Integer("Number of unit between two intervals", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
unit_per_interval = fields.Selection([
@@ -103,9 +124,13 @@ class HolidaysAllocation(models.Model):
nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
_sql_constraints = [
('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL) or (holiday_type='department' AND department_id IS NOT NULL) )",
"The employee, department or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."),
('type_value',
"CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or "
"(holiday_type='category' AND category_id IS NOT NULL) or "
"(holiday_type='department' AND department_id IS NOT NULL) or "
"(holiday_type='company' AND mode_company_id IS NOT NULL))",
"The employee, department, company or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
('duration_check', "CHECK ( number_of_days >= 0 )", "The number of days must be greater than 0."),
('number_per_interval_check', "CHECK(number_per_interval > 0)", "The number per interval should be greater than 0"),
('interval_number_check', "CHECK(interval_number > 0)", "The interval number should be greater than 0"),
]
@@ -161,29 +186,35 @@ class HolidaysAllocation(models.Model):
# As we encode everything in days in the database we need to convert
# the number of hours into days for this we use the
# mean number of hours set on the employee's calendar
days_to_give = days_to_give / holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY
days_to_give = days_to_give / (holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
values['number_of_days_temp'] = holiday.number_of_days_temp + days_to_give * prorata
if holiday.holiday_status_id.balance_limit > 0:
values['number_of_days_temp'] = min(values['number_of_days_temp'], holiday.holiday_status_id.balance_limit)
values['number_of_hours'] = values['number_of_days_temp'] * holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY
values['number_of_days'] = holiday.number_of_days + days_to_give * prorata
if holiday.accrual_limit > 0:
values['number_of_days'] = min(values['number_of_days'], holiday.accrual_limit)
holiday.write(values)
@api.multi
@api.depends('number_of_days_temp', 'type_request_unit', 'number_of_hours', 'holiday_status_id', 'employee_id')
def _compute_number_of_days(self):
for holiday in self:
number_of_days = holiday.number_of_days_temp
if holiday.type_request_unit == 'hour':
# In this case we need the number of days to reflect the number of hours taken
number_of_days = holiday.number_of_hours / holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY
if holiday.holiday_status_id.balance_limit > 0:
number_of_days = min(number_of_days, holiday.holiday_status_id.balance_limit)
@api.depends('number_of_days')
def _compute_number_of_days_display(self):
for allocation in self:
allocation.number_of_days_display = allocation.number_of_days
holiday.number_of_days = number_of_days
@api.multi
def _inverse_number_of_days_display(self):
for allocation in self:
allocation.number_of_days = allocation.number_of_days_display
@api.multi
@api.depends('number_of_days', 'employee_id')
def _compute_number_of_hours_display(self):
for allocation in self:
allocation.number_of_hours_display = allocation.number_of_days * (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
@api.multi
def _inverse_number_of_hours_display(self):
for allocation in self:
allocation.number_of_days = allocation.number_of_hours_display / (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
@api.multi
@api.depends('state', 'employee_id', 'department_id')
@@ -227,20 +258,15 @@ class HolidaysAllocation(models.Model):
if self.holiday_type == 'employee':
self.department_id = self.employee_id.department_id
@api.onchange('number_of_days_temp')
def _onchange_number_of_days_temp(self):
self.number_of_hours = self.number_of_days_temp * self.employee_id.resource_calendar_id.hours_per_day
@api.onchange('number_of_hours')
def _onchange_number_of_hours(self):
self.number_of_days_temp = self.number_of_hours / self.employee_id.resource_calendar_id.hours_per_day
@api.onchange('holiday_status_id')
def _onchange_holiday_status_id(self):
self.date_to = datetime.combine(self.holiday_status_id.validity_stop, time.max)
if self.holiday_status_id.validity_stop and self.date_to:
new_date_to = datetime.combine(self.holiday_status_id.validity_stop, time.max)
if new_date_to < self.date_to:
self.date_to = new_date_to
if self.accrual:
self.number_of_days_temp = 0
self.number_of_days = 0
if self.holiday_status_id.request_unit == 'hour':
self.unit_per_interval = 'hours'
@@ -259,11 +285,24 @@ class HolidaysAllocation(models.Model):
@api.multi
def name_get(self):
res = []
for leave in self:
if leave.type_request_unit == 'hour':
res.append((leave.id, _("Allocation of %s : %.2f hour(s) To %s") % (leave.holiday_status_id.name, leave.number_of_hours, leave.employee_id.name)))
for allocation in self:
if allocation.holiday_type == 'company':
target = allocation.mode_company_id.name
elif allocation.holiday_type == 'department':
target = allocation.department_id.name
elif allocation.holiday_type == 'category':
target = allocation.category_id.name
else:
res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp, leave.employee_id.name)))
target = allocation.employee_id.name
res.append(
(allocation.id,
_("Allocation of %s : %.2f %s to %s") %
(allocation.holiday_status_id.name,
allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days,
'hours' if allocation.type_request_unit == 'hour' else 'days',
target))
)
return res
@api.multi
@@ -329,7 +368,7 @@ class HolidaysAllocation(models.Model):
'holiday_type': 'employee',
'holiday_status_id': self.holiday_status_id.id,
'notes': self.notes,
'number_of_days_temp': self.number_of_days_temp,
'number_of_days': self.number_of_days,
'parent_id': self.id,
'employee_id': employee.id
}
@@ -386,19 +425,32 @@ class HolidaysAllocation(models.Model):
holiday.write({'second_approver_id': current_employee.id})
else:
holiday.write({'first_approver_id': current_employee.id})
if holiday.holiday_type in ['category', 'department']:
leaves = self.env['hr.leave.allocation']
employees = holiday.category_id.employee_ids if holiday.holiday_type == 'category' else holiday.department_id.member_ids
for employee in employees:
values = holiday._prepare_holiday_values(employee)
leaves += self.with_context(mail_notify_force_send=False).create(values)
# TODO is it necessary to interleave the calls?
leaves.action_approve()
if leaves and leaves[0].validation_type == 'both':
leaves.action_validate()
holiday._action_validate_create_childs()
self.activity_update()
return True
def _action_validate_create_childs(self):
childs = self.env['hr.leave.allocation']
if self.state == 'validate' and self.holiday_type in ['category', 'department', 'company']:
if self.holiday_type == 'category':
employees = self.category_id.employee_ids
elif self.holiday_type == 'department':
employees = self.department_id.member_ids
else:
employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)])
for employee in employees:
childs += self.with_context(
mail_notify_force_send=False,
mail_activity_automation_skip=True
).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':
childs.action_validate()
return childs
@api.multi
def action_refuse(self):
current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
+105 -82
View File
@@ -3,6 +3,7 @@
# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
import datetime
import logging
from odoo import api, fields, models
@@ -21,7 +22,8 @@ class HolidaysType(models.Model):
name = fields.Char('Leave Type', required=True, translate=True)
sequence = fields.Integer(default=100,
help='The type with the smallest sequence is the default value in leave request')
categ_id = fields.Many2one('calendar.event.type', string='Meeting Type',
categ_id = fields.Many2one(
'calendar.event.type', string='Meeting Type',
help='Once a leave is validated, Odoo will create a corresponding meeting of this type in the calendar.')
color_name = fields.Selection([
('red', 'Red'),
@@ -41,63 +43,47 @@ class HolidaysType(models.Model):
('wheat', 'Wheat'),
('ivory', 'Ivory')], string='Color in Report', required=True, default='red',
help='This color will be used in the leaves summary located in Reporting > Leaves by Department.')
limit = fields.Boolean('Exceed Allocation',
help="If you select this check box, the system will allow the employees to ask"
"for leaves without allocating some beforehand")
active = fields.Boolean('Active', default=True,
help="If the active field is set to false, it will allow you to hide the leave type without removing it.")
help="If the active field is set to false, it will allow you to hide the leave type without removing it.")
max_leaves = fields.Float(compute='_compute_leaves', string='Maximum Allowed',
help='This value is given by the sum of all leaves requests with a positive value.')
leaves_taken = fields.Float(compute='_compute_leaves', string='Leaves Already Taken',
help='This value is given by the sum of all leaves requests with a positive value.')
leaves_taken = fields.Float(
compute='_compute_leaves', string='Leaves Already Taken',
help='This value is given by the sum of all leaves requests with a negative value.')
remaining_leaves = fields.Float(compute='_compute_leaves', string='Remaining Leaves',
remaining_leaves = fields.Float(
compute='_compute_leaves', string='Remaining Leaves',
help='Maximum Leaves Allowed - Leaves Already Taken')
virtual_remaining_leaves = fields.Float(compute='_compute_leaves', string='Virtual Remaining Leaves',
virtual_remaining_leaves = fields.Float(
compute='_compute_leaves', string='Virtual Remaining Leaves',
help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval')
group_days_allocation = fields.Float(
compute='_compute_group_days_allocation', string='Days Allocated')
group_days_leave = fields.Float(
compute='_compute_group_days_leave', string='Group Leaves')
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id)
validation_type = fields.Selection([('hr', 'Human Resource officer'),
('manager', 'Employee Manager'),
('both', 'Double Validation')],
default='hr',
string='Validation By')
employee_applicability = fields.Selection([('both', 'Both'),
('leave', 'Can be requested'),
('allocation', 'Can be allocated')],
default=lambda self: 'leave' if self.limit else 'both', string='Mode',
help='This leave type will be available on Leave / Allocation request based on selected value')
validation_type = fields.Selection([
('hr', 'Human Resource officer'),
('manager', 'Employee Manager'),
('both', 'Double Validation')], default='hr', string='Validation By')
allocation_type = fields.Selection([
('fixed', 'Fixed by HR'),
('fixed_allocation', 'Fixed by HR + allocation request'),
('no', 'No allocation')],
default='fixed', string='Mode',
help='\tFixed by HR: allocated by HR and cannot be bypassed; users can request leaves;'
'\tFixed by HR + allocation request: allocated by HR and users can request leaves and allocations;'
'\tNo allocation: no allocation by default, users can freely request leaves;')
validity_start = fields.Date("Start Date", default=fields.Date.today,
help='Adding validity to types of leaves so that it cannot be selected outside'
'this time period')
help='Adding validity to types of leaves so that it cannot be selected outside this time period')
validity_stop = fields.Date("End Date")
valid = fields.Boolean(compute='_compute_valid', search='_search_valid', help='This indicates if it is still possible to use this type of leave')
time_type = fields.Selection([('leave', 'Leave'), ('other', 'Other')], default='leave', string="Kind of Leave",
help="Whether this should be computed as a holiday or as work time (eg: formation)")
request_unit = fields.Selection([('day', 'Day'),
('half', 'Half-day'),
('hour', 'Hours')], default='day', string='Take Leaves in', required=True)
accrual = fields.Boolean('Accrual', default=False,
help='This option forces this type of leave to be allocated accrually')
request_unit = fields.Selection([
('day', 'Day'), ('hour', 'Hours')],
default='day', string='Take Leaves in', required=True)
unpaid = fields.Boolean('Is Unpaid', default=False)
negative = fields.Boolean('Allow Negative', help="This option allows to take more leaves than allocated")
balance_limit = fields.Float('Max Balance Limit', default=0, help="The maximum quantity of allocated days on this allocation, zero meaning infinite amount")
_sql_constraints = [
('no_negative_balance_limit', "CHECK(balance_limit >= 0)", "The max balance limit cannot be negative"),
('no_accrual_unpaid', 'CHECK(NOT (accrual AND unpaid))', "A leave type cannot be accrual and considered as unpaid leaves")
]
@api.multi
@api.constrains('validity_start', 'validity_stop')
def _check_validity_dates(self):
@@ -107,29 +93,7 @@ class HolidaysType(models.Model):
raise ValidationError(_("End of validity period should be greater than start of validity period"))
@api.multi
@api.constrains('balance_limit', 'accrual')
def _check_balance_limit(self):
for leave_type in self:
if not leave_type.accrual and leave_type.balance_limit > 0:
raise ValidationError(_("Max balance limit can only be set for accrual leaves"))
@api.onchange('limit')
def _onchange_limit(self):
if self.limit:
self.employee_applicability = 'leave'
self.accrual = False
@api.onchange('accrual')
def _onchange_accrual(self):
if self.accrual:
self.limit = False
self.employee_applicability = 'both'
else:
self.negative = False
self.balance_limit = 0
@api.multi
@api.depends('validity_start', 'validity_stop', 'limit')
@api.depends('validity_start', 'validity_stop')
def _compute_valid(self):
dt = self._context.get('default_date_from') or fields.Datetime.now()
@@ -169,10 +133,10 @@ class HolidaysType(models.Model):
for request in requests:
status_dict = result[request.holiday_status_id.id]
status_dict['virtual_remaining_leaves'] -= request.number_of_days_temp
status_dict['virtual_remaining_leaves'] -= request.number_of_days
if request.state == 'validate':
status_dict['leaves_taken'] += request.number_of_days_temp
status_dict['remaining_leaves'] -= request.number_of_days_temp
status_dict['leaves_taken'] += request.number_of_days
status_dict['remaining_leaves'] -= request.number_of_days
for allocation in allocations:
status_dict = result[allocation.holiday_status_id.id]
@@ -180,19 +144,25 @@ class HolidaysType(models.Model):
# note: add only validated allocation even for the virtual
# count; otherwise pending then refused allocation allow
# the employee to create more leaves than possible
status_dict['virtual_remaining_leaves'] += allocation.number_of_days_temp
status_dict['max_leaves'] += allocation.number_of_days_temp
status_dict['remaining_leaves'] += allocation.number_of_days_temp
status_dict['virtual_remaining_leaves'] += allocation.number_of_days
status_dict['max_leaves'] += allocation.number_of_days
status_dict['remaining_leaves'] += allocation.number_of_days
return result
def _get_contextual_employee_id(self):
if 'employee_id' in self._context:
employee_id = self._context['employee_id']
elif 'default_employee_id' in self._context:
employee_id = self._context['default_employee_id']
else:
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.user.id)], limit=1).id
return employee_id
@api.multi
def _compute_leaves(self):
data_days = {}
if 'employee_id' in self._context:
employee_id = self._context['employee_id']
else:
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.user.id)], limit=1).id
employee_id = self._get_contextual_employee_id()
if employee_id:
data_days = self.get_days(employee_id)
@@ -204,6 +174,30 @@ class HolidaysType(models.Model):
holiday_status.remaining_leaves = result.get('remaining_leaves', 0)
holiday_status.virtual_remaining_leaves = result.get('virtual_remaining_leaves', 0)
@api.multi
def _compute_group_days_allocation(self):
grouped_res = self.env['hr.leave.allocation'].read_group(
[('holiday_status_id', 'in', self.ids), ('holiday_type', '!=', 'employee'), ('state', '=', 'validate'),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))],
['holiday_status_id', 'number_of_days'],
['holiday_status_id'],
)
grouped_dict = dict((data['holiday_status_id'][0], data['number_of_days']) for data in grouped_res)
for allocation in self:
allocation.group_days_allocation = grouped_dict.get(allocation.id, 0)
@api.multi
def _compute_group_days_leave(self):
grouped_res = self.env['hr.leave'].read_group(
[('holiday_status_id', 'in', self.ids), ('holiday_type', '=', 'employee'), ('state', '=', 'validate'),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))],
['holiday_status_id'],
['holiday_status_id'],
)
grouped_dict = dict((data['holiday_status_id'][0], data['holiday_status_id_count']) for data in grouped_res)
for allocation in self:
allocation.group_days_leave = grouped_dict.get(allocation.id, 0)
@api.multi
def name_get(self):
if not self._context.get('employee_id'):
@@ -212,7 +206,7 @@ class HolidaysType(models.Model):
res = []
for record in self:
name = record.name
if not record.limit:
if record.allocation_type != 'no':
name = "%(name)s (%(count)s)" % {
'name': name,
'count': _('%g remaining out of %g') % (float_round(record.virtual_remaining_leaves, precision_digits=2) or 0.0, float_round(record.max_leaves, precision_digits=2) or 0.0)
@@ -225,7 +219,7 @@ class HolidaysType(models.Model):
""" Override _search to order the results, according to some employee.
The order is the following
- limit (limited leaves first, such as Legal Leaves)
- allocation fixed first, then allowing allocation, then free allocation
- virtual remaining leaves (higher the better, so using reverse on sorted)
This override is necessary because those fields are not stored and depends
@@ -233,9 +227,38 @@ class HolidaysType(models.Model):
is an employee_id in context and that no other order has been given
to the method.
"""
employee_id = self._get_contextual_employee_id()
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 self._context.get('employee_id'):
if not count and not order and employee_id:
leaves = self.browse(leave_ids)
sort_key = lambda l: (not l.limit, l.virtual_remaining_leaves)
sort_key = lambda l: (l.allocation_type == 'fixed', l.allocation_type == 'fixed_allocation', l.virtual_remaining_leaves)
return leaves.sorted(key=sort_key, reverse=True).ids
return leave_ids
@api.multi
def action_see_days_allocated(self):
self.ensure_one()
action = self.env.ref('hr_holidays.hr_leave_allocation_action_all').read()[0]
action['domain'] = [
('holiday_type', '!=', 'employee'),
('holiday_status_id', '=', self.ids[0]),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))
]
action['context'] = {
'default_holiday_type': 'department',
'default_holiday_status_id': self.ids[0],
}
return action
@api.multi
def action_see_group_leaves(self):
self.ensure_one()
action = self.env.ref('hr_holidays.hr_leave_action_all').read()[0]
action['domain'] = [
('holiday_status_id', '=', self.ids[0]),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))
]
action['context'] = {
'default_holiday_status_id': self.ids[0],
}
return action
@@ -74,7 +74,7 @@ class HrHolidaySummaryReport(models.AbstractModel):
if date_from >= start_date and date_from <= end_date:
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
date_from += timedelta(1)
count += abs(holiday.number_of_days)
count += holiday.number_of_days
self.sum = count
return res
+1 -1
View File
@@ -65,7 +65,7 @@ class LeaveReport(models.Model):
union all select
request.employee_id as employee_id,
request.name as name,
request.number_of_days as number_of_days,
(request.number_of_days * -1) as number_of_days,
request.category_id as category_id,
request.department_id as department_id,
request.holiday_status_id as holiday_status_id,
+6 -10
View File
@@ -121,13 +121,6 @@
'search_default_group_type': 1,
'search_default_year': 1 ,
'search_default_my_leaves': 1,
'needaction_menu_ref':
[
'hr_holidays.menu_open_ask_holidays_new',
'hr_holidays.menu_open_allocation_holidays',
'hr_holidays.menu_request_approve_allocation',
'hr_holidays.menu_open_employee_leave',
]
}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -145,15 +138,18 @@
<field name="src_model">hr.employee</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id, 'search_default_group_type': 1,
<field name="context">{
'search_default_employee_id': [active_id],
'default_employee_id': active_id,
'search_default_group_type': 1,
'search_default_year': 1}</field>
<field name="domain">[('holiday_type','=','employee'), ('holiday_status_id.limit', '=', False), ('state','!=', 'refuse')]</field>
<field name="domain">[('holiday_type','=','employee')]</field>
<field name="search_view_id" ref="view_hr_holidays_filter_report"/>
</record>
<menuitem
id="menu_open_company_allocation"
name="Leaves Summary"
name="Summary"
parent="menu_hr_holidays_my_leaves"
action="open_company_allocation"
sequence="21"/>
+11 -8
View File
@@ -9,38 +9,41 @@ class TestHrHolidaysBase(common.TransactionCase):
def setUp(self):
super(TestHrHolidaysBase, self).setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
# Find Employee group
group_employee_id = self.ref('base.group_user')
self._quick_create_ctx = {
'mail_create_nolog': True,
'mail_create_nosubscribe': True,
'mail_notrack': True,
}
self._quick_create_user_ctx = dict(self._quick_create_ctx, no_reset_password=True)
# Test users to use through the various tests
Users = self.env['res.users'].with_context(self._quick_create_user_ctx)
self.user_hruser = Users.create({
'name': 'Armande HrUser',
'login': 'Armande',
'email': 'armande.hruser@example.com',
'groups_id': [(6, 0, [group_employee_id, self.ref('hr_holidays.group_hr_holidays_user')])]
'groups_id': [(6, 0, [self.ref('base.group_user'), self.ref('hr_holidays.group_hr_holidays_user')])]
})
self.user_hruser_id = self.user_hruser.id
self.user_hrmanager = Users.create({
'name': 'Bastien HrManager',
'login': 'bastien',
'email': 'bastien.hrmanager@example.com',
'groups_id': [(6, 0, [group_employee_id, self.ref('hr_holidays.group_hr_holidays_manager')])]
'groups_id': [(6, 0, [self.ref('base.group_user'), self.ref('hr_holidays.group_hr_holidays_manager')])]
}).id
self.user_hrmanager_id = self.user_hrmanager
self.user_employee = Users.create({
'name': 'David Employee',
'login': 'david',
'email': 'david.employee@example.com',
'groups_id': [(6, 0, [group_employee_id])]
'groups_id': [(6, 0, [self.ref('base.group_user')])]
})
self.user_employee_id = self.user_employee.id
self.user_hrmanager_2_id = Users.create({
'name': 'Florence HrManager',
'login': 'florence',
'email': 'florence.hrmanager@example.com',
'groups_id': [(6, 0, [group_employee_id, self.ref('hr_holidays.group_hr_holidays_manager')])]
'groups_id': [(6, 0, [self.ref('base.group_user'), self.ref('hr_holidays.group_hr_holidays_manager')])]
}).id
# Hr Data
+12 -12
View File
@@ -17,8 +17,8 @@ class TestLeavesRights(TestHrHolidaysBase):
super(TestLeavesRights, self).setUp()
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited',
'limit': True,
'validation_type': 'hr',
'allocation_type': 'no',
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -30,7 +30,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_emp.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@@ -60,9 +60,9 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_hruser.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
res = other_leave.sudo(self.user_employee_id).read(['number_of_days_temp', 'state', 'name'])
res = other_leave.sudo(self.user_employee_id).read(['number_of_days', 'state', 'name'])
self.assertEqual(
res[0]['name'], '*****',
'Private information should have been stripped, received %s instead' % res[0]['name']
@@ -78,7 +78,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_hruser.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
self.assertEqual(
other_leave.sudo(self.user_employee_id).name, '*****',
@@ -88,7 +88,7 @@ class TestLeavesRights(TestHrHolidaysBase):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_read_by_user_own(self):
""" Users should be able to read name field of own requests """
res = self.employee_leave.read(['name', 'number_of_days_temp', 'state'])
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')
@@ -112,7 +112,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_hruser.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
with self.assertRaises(AccessError):
other_leave.sudo(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'})
@@ -131,7 +131,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_hrmanager.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
manager_leave.sudo(self.user_hrmanager).action_draft()
@@ -150,7 +150,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_hruser.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
officer_leave.sudo(self.user_hruser).action_draft()
@@ -175,7 +175,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'employee_id': self.employee_hruser.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
with self.assertRaises(UserError):
other_leave.sudo(self.user_employee_id).action_draft()
@@ -316,9 +316,9 @@ class TestMultiCompany(TestHrHolidaysBase):
})
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited - Company New',
'limit': True,
'company_id': self.new_company.id,
'validation_type': 'hr',
'allocation_type': 'no',
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -330,7 +330,7 @@ class TestMultiCompany(TestHrHolidaysBase):
'employee_id': self.employee_emp.id,
'date_from': datetime.now(),
'date_to': datetime.now() + relativedelta(days=1),
'number_of_days_temp': 1,
'number_of_days': 1,
})
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@@ -19,13 +19,12 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.accrual_type = LeaveType.create({
'name': 'accrual',
'limit': False,
'accrual': True,
'allocation_type': 'fixed',
})
self.unpaid_type = LeaveType.create({
'name': 'unpaid',
'limit': True,
'allocation_type': 'no',
'unpaid': True,
})
@@ -45,67 +44,57 @@ class TestAccrualAllocations(TestHrHolidaysBase):
""" % (newdate, id))
def test_accrual_base_no_leaves(self):
"""
Test if we can allocate some leaves accrually to an employee
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
""" Test if we can allocate some leaves accrually to an employee """
alloc = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc.action_approve()
alloc._update_accrual()
alloc_0.action_approve()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 1, 'Employee should have been allocated one leave day')
self.assertEqual(alloc.number_of_days, 1, 'Employee should have been allocated one leave day')
def test_accrual_base_leaves(self):
"""
Test if the accrual allocation take the unpaid leaves into account
when allocating leaves
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
Leave = self.env['hr.leave'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
""" Test if the accrual allocation take the unpaid leaves into account when allocating leaves """
alloc = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee with leaves',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc_0.action_approve()
alloc.action_approve()
employee = self.env['hr.employee'].browse(self.employee_hruser_id)
# Getting the previous work date
df = employee.resource_calendar_id.plan_days(-2, fields.Datetime.now()).date()
leave_0 = Leave.create({
leave = self.env['hr.leave'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Leave for hruser',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.unpaid_type.id,
'date_from': datetime.combine(df, time(0, 0, 0)),
'date_to': datetime.combine(df + relativedelta(days=1), time(0, 0, 0)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
leave_0.action_approve()
leave.action_approve()
Allocation._update_accrual()
alloc._update_accrual()
self.assertEqual(alloc_0.number_of_days, .8, 'As employee took some unpaid leaves last week, he should be allocated only .8 days')
self.assertEqual(alloc.number_of_days, .8, 'As employee took some unpaid leaves last week, he should be allocated only .8 days')
def test_accrual_many(self):
"""
@@ -117,7 +106,8 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '1 day per 2 weeks',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 2,
'unit_per_interval': 'days',
@@ -128,7 +118,8 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '4 hours per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 4,
'interval_number': 1,
'unit_per_interval': 'hours',
@@ -139,7 +130,8 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '2 day per 1 month',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 2,
'interval_number': 1,
'unit_per_interval': 'days',
@@ -150,7 +142,8 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '20 days per year',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 20,
'interval_number': 1,
'unit_per_interval': 'days',
@@ -179,7 +172,8 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': 'one shot one kill',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
@@ -193,29 +187,23 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.assertEqual(alloc_0.number_of_days, 0, 'Employee is new he should not get any accrual leaves')
def test_accrual_multi(self):
"""
Test if the cron does not allocate leaves every time it's called but only
when necessary
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
""" Test if the cron does not allocate leaves every time it's called but only when necessary """
alloc = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': '2 days per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'accrual': True,
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 2,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc.action_approve()
alloc._update_accrual()
alloc._update_accrual()
alloc_0.action_approve()
Allocation._update_accrual()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 1, 'Cron only allocates 1 days every two weeks')
self.assertEqual(alloc.number_of_days, 1, 'Cron only allocates 1 days every two weeks')
def test_accrual_validation(self):
"""
@@ -227,7 +215,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '20 days per year',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days_temp': 0,
'number_of_days': 0,
'number_per_interval': 20,
'interval_number': 1,
'unit_per_interval': 'days',
@@ -242,32 +230,20 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.assertEqual(alloc_0.number_of_days, 0, 'Cron validity passed, should not allocate any leave')
def test_accrual_balance_limit(self):
"""
Test if accrual allocation does not allocate more than the balance limit
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
LeaveType = self.env['hr.leave.type'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
accrual_max_five = LeaveType.create({
'name': 'max 5 days',
'limit': False,
'accrual': True,
'balance_limit': 5,
})
allocation = Allocation.create({
""" Test if accrual allocation does not allocate more than the balance limit"""
allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'accrual 5 max',
'employee_id': self.employee_emp_id,
'holiday_status_id': accrual_max_five.id,
'number_of_days_temp': 0,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'accrual_limit': 5,
'number_of_days': 0,
'number_per_interval': 6,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
allocation.action_approve()
Allocation._update_accrual()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 5, 'Should have allocated only 5 days as balance limit is 5')
+20 -20
View File
@@ -33,34 +33,34 @@ class TestHolidaysFlow(TestHrHolidaysBase):
with self.assertRaises(AccessError):
HolidaysStatus.sudo(self.user_hruser_id).create({
'name': 'UserCheats',
'limit': True,
'allocation_type': 'no',
})
# HrManager creates some holiday statuses
HolidayStatusManagerGroup = HolidaysStatus.sudo(self.user_hrmanager_id)
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'limit': True,
'allocation_type': 'no',
'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id
})
self.holidays_status_1 = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'limit': True,
'allocation_type': 'no',
'validation_type': 'hr',
})
self.holidays_status_2 = HolidayStatusManagerGroup.create({
'name': 'Limited',
'limit': False,
'allocation_type': 'fixed',
'validation_type': 'both',
})
self.holidays_status_3 = HolidayStatusManagerGroup.create({
'name': 'NotLimitedManager',
'limit': True,
'allocation_type': 'no',
'validation_type': 'manager',
})
self.holiday_status_4 = HolidayStatusManagerGroup.create({
'name': 'TimeNotLimited',
'limit': True,
'allocation_type': 'no',
'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'),
@@ -81,7 +81,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_1.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': datetime.today(),
'number_of_days_temp': 1,
'number_of_days': 1,
})
hol1_user_group = hol1_employee_group.sudo(self.user_hruser_id)
hol1_manager_group = hol1_employee_group.sudo(self.user_hrmanager_id)
@@ -101,7 +101,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_3.id,
'date_from': (datetime.today() + relativedelta(days=12)),
'date_to': (datetime.today() + relativedelta(days=13)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
hol12_user_group = hol12_employee_group.sudo(self.user_hruser_id)
hol12_manager_group = hol12_employee_group.sudo(self.user_hrmanager_id)
@@ -126,7 +126,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_1.id,
'date_from': (datetime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
'date_to': datetime.today(),
'number_of_days_temp': 1,
'number_of_days': 1,
})
# Employee creates a leave request in a limited category -> crash, not enough days left
@@ -137,7 +137,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_2.id,
'date_from': (datetime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=2)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
# Clean transaction
@@ -148,7 +148,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'name': 'Days for limited category',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.holidays_status_2.id,
'number_of_days_temp': 2,
'number_of_days': 2,
})
# HrUser validates the first step
aloc1_user_group.action_approve()
@@ -166,7 +166,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_2.id,
'date_from': (datetime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=3)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
hol2_user_group = hol2.sudo(self.user_hruser_id)
# Check left days: - 1 virtual remaining day
@@ -205,7 +205,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
hol2_manager_group.write({
'date_from': (datetime.today() + relativedelta(days=4)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=7)),
'number_of_days_temp': 4,
'number_of_days': 4,
})
with self.assertRaises(ValidationError):
hol2_manager_group.action_confirm()
@@ -220,7 +220,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'date_from': datetime.today().strftime('%Y-%m-10 10:00:00'),
'date_to': datetime.today().strftime('%Y-%m-11 19:00:00'),
'employee_id': employee_id,
'number_of_days_temp': 1,
'number_of_days': 1,
})
# I find a small mistake on my leave request to I click on "Refuse" button to correct a mistake.
hol3.action_refuse()
@@ -242,7 +242,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'date_from': fields.Datetime.from_string('2017-03-03 06:00:00'),
'date_to': fields.Datetime.from_string('2017-03-11 19:00:00'),
'employee_id': employee_id,
'number_of_days_temp': 1,
'number_of_days': 1,
}).unlink()
with self.assertRaises(ValidationError):
@@ -252,7 +252,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'date_from': fields.Datetime.from_string('2017-07-03 06:00:00'),
'date_to': fields.Datetime.from_string('2017-07-11 19:00:00'),
'employee_id': employee_id,
'number_of_days_temp': 1,
'number_of_days': 1,
})
hol41 = HolidaysEmployeeGroup.create({
@@ -261,7 +261,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_1.id,
'date_from': (datetime.today() + relativedelta(days=9)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=10)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
# A simple user should be able to reset it's own leave
@@ -274,7 +274,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_1.id,
'date_from': (datetime.today() + relativedelta(days=9)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=10)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
# A manager should be able to reset someone else's leave
@@ -288,7 +288,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_1.id,
'date_from': (datetime.today() + relativedelta(days=15)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=16)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
hol51.action_approve()
@@ -300,7 +300,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'holiday_status_id': self.holidays_status_1.id,
'date_from': (datetime.today() + relativedelta(days=15)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=16)),
'number_of_days_temp': 1,
'number_of_days': 1,
})
hol52.action_approve()
+17 -26
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil.relativedelta import relativedelta
@@ -7,35 +8,25 @@ from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
class TestHrLeaveType(TestHrHolidaysBase):
def setUp(self):
super(TestHrLeaveType, self).setUp()
LeaveType = self.env['hr.leave.type']
self.leave_leave = LeaveType.create({
'name': 'Legal Leaves',
'limit': True,
'time_type': 'leave',
})
def test_time_type(self):
Leave = self.env['hr.leave']
RLeave = self.env['resource.calendar.leaves']
leave_1 = Leave.create({
'name': 'Doctor Appointment',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.leave_leave.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': datetime.today(),
'number_of_days_temp': 1,
leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
})
leave_1 = self.env['hr.leave'].create({
'name': 'Doctor Appointment',
'employee_id': self.employee_hruser_id,
'holiday_status_id': leave_type.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': datetime.today(),
'number_of_days': 1,
})
leave_1.action_approve()
self.assertEqual(RLeave.search([('holiday_id', '=', leave_1.id)]).time_type, 'leave')
leave_1.action_refuse()
leave_1.action_draft()
leave_1.unlink()
self.assertEqual(
self.env['resource.calendar.leaves'].search([('holiday_id', '=', leave_1.id)]).time_type,
'leave'
)
@@ -47,65 +47,48 @@
</field>
</record>
<record id="edit_new_allocation" model="ir.ui.view">
<field name="name">Allocation Request</field>
<record id="hr_leave_allocation_view_form" model="ir.ui.view">
<field name="name">hr.leave.allocation.view.form</field>
<field name="model">hr.leave.allocation</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<form string="Allocation Request">
<field name="can_reset" invisible="1"/>
<field name="can_approve" invisible="1"/>
<field name="accrual" invisible="1"/>
<header>
<button string="Confirm" name="action_confirm" states="draft" type="object" class="oe_highlight"/>
<button string="Approve" name="action_approve" states="confirm" type="object" groups="hr_holidays.group_hr_holidays_user" class="oe_highlight"
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', '!=', 'confirm')]}"/>
<button string="Validate" name="action_validate" states="validate1" type="object" groups="hr_holidays.group_hr_holidays_manager" class="oe_highlight"/>
<button string="Refuse" name="action_refuse" states="confirm,validate,validate1" type="object" groups="hr_holidays.group_hr_holidays_user"
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', 'not in', ('confirm','validate','validate1'))]}"/>
<button string="Reset to Draft" name="action_draft" type="object"
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="display_name"/></h1>
<h2><field name="display_name"/></h2>
</div>
<group>
<group>
<field name="type_request_unit" invisible="1"/>
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"
domain="[('valid', '=', True), ('allocation_type', 'in', ['fixed_allocation', 'no'])]"/>
<label for="number_of_days_temp" string="Duration" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '=', 'hour'),('accrual', '=', True)]}"/>
<div attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '=', 'hour'),('accrual', '=', True)]}">
<field name="number_of_days_temp" class="oe_inline" nolabel="1" string="Duration"/>
<span class="ml8">Days</span>
<field name="number_of_days" invisible="1"/>
<label for="number_of_days" string="Duration"/>
<div>
<field name="number_of_days_display" class="oe_inline" nolabel="1"
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('type_request_unit', '=', 'hour')]}"/>
<field name="number_of_hours_display" class="oe_inline" nolabel="1"
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('type_request_unit', '!=', 'hour')]}"/>
<span class="ml8" attrs="{'invisible': [('type_request_unit', '=', 'hour')]}">Days</span>
<span class="ml8" attrs="{'invisible': [('type_request_unit', '!=', 'hour')]}">Hours</span>
</div>
<label for="number_of_hours" string="Duration" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '!=', 'hour'),('accrual', '=', True)]}"/>
<div attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '!=', 'hour'),('accrual', '=', True)]}">
<field name="number_of_hours" class="oe_inline" nolabel="1" string="Duration"/>
<span class="ml8">Hours</span>
</div>
<field name="accrual" invisible="1"/>
</group>
<group>
<field name="holiday_type" string="Mode" groups="hr_holidays.group_hr_holidays_user" context="{'employee_id':employee_id}" />
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','!=','employee')]}" groups="hr_holidays.group_hr_holidays_user"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'invisible':[('holiday_type','!=','category')]}"/>
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" attrs="{'invisible':[('holiday_type','=','category')]}"/>
<group name="alloc_right_col">
<field name="employee_id" invisible="1" groups="hr_holidays.group_hr_holidays_user"/>
<field name="department_id" invisible="1"/>
</group>
<group attrs="{'invisible': [('accrual', '=', False)]}">
<field name="date_to" string="Run Until" help="If no value set, runs indefinitely" class="oe_inline"/>
<div class="o_row" colspan="2">
Add <field name="number_per_interval" attrs="{'required': [('accrual', '=', True)]}"/>
<field name="unit_per_interval" attrs="{'required': [('accrual', '=', True)]}"/> of leaves every
<field name="interval_number" attrs="{'required': [('accrual', '=', True)]}"/>
<field name="interval_unit" attrs="{'required': [('accrual', '=', True)]}"/>
</div>
</group>
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
</group>
<field name="notes" nolabel="1" placeholder="Add a reason..."/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
@@ -116,9 +99,75 @@
</field>
</record>
<record id="view_allocation_tree" model="ir.ui.view">
<field name="name">hr.leave.allocation.tree</field>
<record id="hr_leave_allocation_view_form_manager" model="ir.ui.view">
<field name="name">hr.leave.allocation.view.form.manager</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
<field name="mode">primary</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_confirm']" position="after">
<button string="Approve" name="action_approve" states="confirm" type="object" groups="hr_holidays.group_hr_holidays_user" class="oe_highlight"
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', '!=', 'confirm')]}"/>
<button string="Validate" name="action_validate" states="validate1" type="object" groups="hr_holidays.group_hr_holidays_manager" class="oe_highlight"/>
<button string="Refuse" name="action_refuse" states="confirm,validate,validate1" type="object" groups="hr_holidays.group_hr_holidays_user"
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', 'not in', ('confirm','validate','validate1'))]}"/>
<button string="Reset to Draft" name="action_draft" type="object"
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
</xpath>
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
<attribute name="domain">[('valid', '=', True)]</attribute>
</xpath>
<xpath expr="//field[@name='employee_id']" position="before">
<field name="holiday_type" string="Mode" groups="hr_holidays.group_hr_holidays_user" context="{'employee_id':employee_id}" />
</xpath>
<xpath expr="//field[@name='employee_id']" position="replace">
<field name="employee_id" groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'employee')], 'invisible': [('holiday_type', '!=', 'employee')]}"/>
</xpath>
<xpath expr="//field[@name='employee_id']" position="after">
<field name="category_id"
attrs="{'required': [('holiday_type', '=', 'category')], 'invisible': [('holiday_type', '!=', 'category')]}"/>
</xpath>
<xpath expr="//field[@name='department_id']" position="replace">
<field name="department_id" groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'department')], 'invisible': [('holiday_type', 'not in', ('department', 'category'))]}"/>
</xpath>
<xpath expr="//field[@name='department_id']" position="after">
<field name="mode_company_id" groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'company')], 'invisible': [('holiday_type', '!=', 'company')]}"/>
</xpath>
<xpath expr="//field[@name='accrual']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//field[@name='accrual']" position="after">
<label for="date_to" string=""
attrs="{'invisible': [('accrual', '=', False)]}"/>
<div attrs="{'invisible': [('accrual', '=', False)]}">
<div class="o_row">
Run until <field name="date_to" string="Run Until" help="If no value set, runs indefinitely"/>
</div>
<div class="o_row">
<span>Add</span>
<field name="number_per_interval" class="ml8"
attrs="{'required': [('accrual', '=', True)]}"/>
<field name="unit_per_interval"
attrs="{'required': [('accrual', '=', True)]}"/>
<span class="ml8">of leaves every</span>
<field name="interval_number" class="ml8"
attrs="{'required': [('accrual', '=', True)]}"/>
<field name="interval_unit"
attrs="{'required': [('accrual', '=', True)]}"/>
</div>
</div>
</xpath>
</field>
</record>
<record id="hr_leave_allocation_view_tree" model="ir.ui.view">
<field name="name">hr.leave.allocation.view.tree</field>
<field name="model">hr.leave.allocation</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<tree decoration-danger="state == 'refuse'" decoration-info="state == 'draft'" string="Allocation Requests"
decoration-bf="message_needaction == True">
@@ -129,28 +178,118 @@
<field name="number_of_days" string="Allocated Days" sum="Total allocated days"/>
<field name="message_needaction" invisible="1"/>
<field name="state"/>
<button string="Approve" name="action_approve" type="object"
icon="fa-check text-success"
states="confirm"
groups="hr_holidays.group_hr_holidays_user"/>
<button string="Validate" name="action_validate" type="object"
icon="fa-check text-success"
states="validate1"
groups="hr_holidays.group_hr_holidays_manager"/>
<button string="Refuse" name="action_refuse" type="object"
icon="fa-ban text-danger"
states="confirm,validate,validate1"
groups="hr_holidays.group_hr_holidays_manager"/>
</tree>
</field>
</record>
<record id="hr_holidays_view_list_my_allocation" model="ir.ui.view">
<field name="name">hr.holidays.my.allocation.tree</field>
<record id="hr_leave_allocation_view_tree_my" model="ir.ui.view">
<field name="name">hr.leave.allocation.view.tree.my</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_leave_allocation_view_tree"/>
<field name="mode">primary</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='employee_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_approve']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_validate']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_refuse']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
<record id="hr_leave_allocation_view_kanban" model="ir.ui.view">
<field name="name">hr.leave.allocation.view.kanban</field>
<field name="model">hr.leave.allocation</field>
<field name="arch" type="xml">
<tree decoration-danger="state == 'refuse'" decoration-info="state == 'draft'" string="Allocation Requests"
decoration-bf="message_needaction == True">
<kanban class="o_kanban_mobile" create="0">
<field name="employee_id"/>
<field name="holiday_status_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="name"/>
<field name="number_of_days" string="Allocated Days" sum="Total allocated days"/>
<field name="message_needaction" invisible="1"/>
<field name="state"/>
</tree>
<field name="number_of_days"/>
<field name="can_approve"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div>
<span>
<img t-att-src="kanban_image('hr.employee', 'image_small', record.employee_id.raw_value)" t-att-title="record.employee_id.value" t-att-alt="record.employee_id.value" width="40" height="40" class="oe_kanban_avatar float-left mr4"/>
</span>
<div class="row">
<div class="col-8">
<strong class="o_kanban_record_title"><t t-esc="record.employee_id.value"/></strong>
</div>
<div class="col-4">
<span class="float-right badge badge-secondary">
<field name="state" widget="kanban_label_selection"
options="{'classes': {'draft': 'default', 'confirm': 'default', 'validate': 'success', 'cancel': 'danger'}}"/>
</span>
</div>
</div>
<div class="row">
<div class="col-12 text-muted o_kanban_record_subtitle">
<t t-esc="record.name.value"/>
</div>
</div>
<div class="o_dropdown_kanban dropdown" groups="base.group_user">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit Task</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<a t-if="record.can_approve.value" name="action_approve" type="object"
role="menuitem" class="dropdown-item">Approve</a>
<a t-if="record.can_approve.value" name="action_refuse" type="object"
role="menuitem" class="dropdown-item">Refuse</a>
</div>
</div>
</div>
<div class="o_kanban_record_bottom mt8 mb4">
<div t-attf-class="oe_kanban_bottom_left #{record.date_from.value ? 'mt8 mb4': ''}">
<table class="text-right" t-if="record.date_from.value">
<tr>
<td style="padding-bottom:4px"><small class="text-muted">from</small></td>
<td style="padding:0 0 4px 4px"><t t-esc="record.date_from.value"/></td>
</tr>
<tr>
<td><small class="text-muted">to</small></td>
<td style="padding-left:4px"><t t-esc="record.date_to.value"/></td>
</tr>
</table>
</div>
<div t-attf-class="oe_kanban_bottom_right #{record.date_from.value ? 'mt8': ''}">
<span class="badge badge-pill"><t t-esc="record.number_of_days.value"/> days</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
</record>
<record id="open_allocation" model="ir.actions.act_window">
<field name="name">Allocation Request</field>
<record id="hr_leave_allocation_action_my" model="ir.actions.act_window">
<field name="name">My Allocations</field>
<field name="res_model">hr.leave.allocation</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
@@ -161,24 +300,36 @@
</field>
<field name="context">{}</field>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="search_view_id" ref="view_hr_leave_allocation_filter"/>
</record>
<record id="hr_leave_allocation_action_my_view_tree" model="ir.actions.act_window.view">
<field name="sequence">1</field>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="hr_leave_allocation_action_my"/>
<field name="view_id" ref="hr_leave_allocation_view_tree_my"/>
</record>
<record id="hr_leave_allocation_action_my_view_form" model="ir.actions.act_window.view">
<field name="sequence">2</field>
<field name="view_mode">form</field>
<field name="act_window_id" ref="hr_leave_allocation_action_my"/>
<field name="view_id" ref="hr_leave_allocation_view_form"/>
</record>
<record id="request_approve_allocation" model="ir.actions.act_window">
<field name="name">Allocation Request</field>
<record id="hr_leave_allocation_action_all" model="ir.actions.act_window">
<field name="name">All Allocations</field>
<field name="res_model">hr.leave.allocation</field>
<field name="view_type">form</field>
<field name="context">{
'search_default_approve':1,
}</field>
<field name="view_mode">tree,kanban,form</field>
<field name="context">{}</field>
<field name="domain">[]</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="search_view_id" ref="view_hr_leave_allocation_filter"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave allocation request
</p>
</field>
</record>
<record id="open_department_holidays_allocation_approve" model="ir.actions.act_window">
<field name="name">Department Leaves Allocation</field>
<record id="hr_leave_allocation_action_approve_department" model="ir.actions.act_window">
<field name="name">Department Allocation to Approve</field>
<field name="res_model">hr.leave.allocation</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban</field>
@@ -189,87 +340,28 @@
Create a new leave allocation
</p>
</field>
<field name="search_view_id" ref="view_hr_leave_allocation_filter"/>
</record>
<record id="hr_holidays_action_allocation_from_department" model="ir.actions.act_window">
<field name="name">Allocation Requests to Approve</field>
<field name="res_model">hr.leave.allocation</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{
'search_default_department_id': [active_id],
'default_department_id': active_id,
'search_default_approve':1}
</field>
<field name="domain">[]</field>
<field name="search_view_id" ref="view_hr_leave_allocation_filter"/>
</record>
<record id="action_open_allocation_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_allocation_tree"/>
<field name="act_window_id" ref="open_allocation"/>
</record>
<record id="action_open_allocation_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="act_window_id" ref="open_allocation"/>
</record>
<record id="action_request_approve_allocation_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_allocation_tree"/>
<field name="act_window_id" ref="request_approve_allocation"/>
</record>
<record id="action_request_approve_allocation_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="act_window_id" ref="request_approve_allocation"/>
</record>
<record id="request_approve_holidays" model="ir.actions.act_window">
<field name="name">Requests to Approve</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="context">{
'search_default_approve':1,
}</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="action_request_approve_holidays_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_holiday"/>
<field name="act_window_id" ref="request_approve_holidays"/>
</record>
<record id="action_request_approve_holidays_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="act_window_id" ref="request_approve_holidays"/>
</record>
<menuitem
id="menu_open_department_leave_allocation_approve" groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
name="Allocations to Approve"
parent="menu_hr_holidays_approvals"
action="open_department_holidays_allocation_approve"
sequence="2"/>
<menuitem
id="menu_open_allocation"
name="Allocations Requests"
parent="menu_hr_holidays_my_leaves"
action="open_allocation"
sequence="30"/>
action="hr_leave_allocation_action_my"
sequence="6"/>
<menuitem
id="hr_holidays_menu_manager_approve_allocations"
name="Allocations"
parent="hr_holidays_menu_manager_approve"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="hr_leave_allocation_action_approve_department"
sequence="2"/>
<menuitem
id="hr_holidays_menu_manager_all_allocations"
name="Allocations"
parent="hr_holidays_menu_manager_all"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="hr_leave_allocation_action_all"
sequence="2"/>
</odoo>
@@ -1,7 +1,6 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<!-- Holidays status -->
<record id="view_holidays_status_filter" model="ir.ui.view">
<field name="name">hr.leave.type.filter</field>
<field name="model">hr.leave.type</field>
@@ -20,27 +19,40 @@
<form string="Leave 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">
<div class="o_stat_info">
<field name="group_days_allocation"/>
<span class="o_stat_text">Days Allocated</span>
</div>
</button>
<button class="oe_stat_button" type="object" name="action_see_group_leaves" icon="fa-archive">
<div class="o_stat_info">
<field name="group_days_leave"/>
<span class="o_stat_text">Group Leaves</span>
</div>
</button>
<button class="oe_stat_button" type="object" name="toggle_active" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<div class="oe_title">
<h1><field name="name"/></h1>
</div>
<group>
<group name="description" string="Description">
<field name="name"/>
<group name="description" string="Options">
<field name="unpaid"/>
<field name="limit" attrs="{'invisible': [('accrual', '=', True)]}"/>
<field name="request_unit"/>
<field name="sequence" attrs="{'invisible': [('limit', '!=', False)]}" groups="base.group_no_one"/>
<field name="request_unit" widget="radio"/>
<field name="sequence" groups="base.group_no_one"/>
<field name="time_type" groups="base.group_no_one"/>
</group>
<group name="validation" string="Validation">
<field name="validation_type" widget="radio"/>
</group>
<group name="visibility" string="Visibility">
<field name="employee_applicability" widget="radio" attrs="{'readonly': ['|', ('limit', '=', True), ('accrual', '=', True)]}" force_save="1"/>
<group name="visibility" string="Allocation">
<field name="allocation_type" widget="radio" force_save="1"/>
</group>
<group name="validity" string="Validity" attrs="{'invisible': [('employee_applicability', '=', 'allocation')]}">
<group name="validity" string="Validity">
<field name="validity_start"/>
<field name="validity_stop"/>
</group>
@@ -49,30 +61,12 @@
<field name="color_name"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group name="allocation_request" string="Allocation Type" attrs="{'invisible': [('unpaid', '=', True)]}">
<field name="accrual"/>
<field name="negative" attrs="{'invisible': [('accrual', '=', False)]}"/>
<field name="balance_limit" attrs="{'invisible': [('accrual', '=', False)]}"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_holiday_status_tree" model="ir.ui.view">
<field name="name">hr.leave.type.tree</field>
<field name="model">hr.leave.type</field>
<field name="arch" type="xml">
<tree string="Leave Type">
<field name="name"/>
<field name="max_leaves"/>
<field name="leaves_taken"/>
<field name="remaining_leaves"/>
</tree>
</field>
</record>
<record id="hr_holiday_status_view_kanban" model="ir.ui.view">
<field name="name">hr.leave.type.kanban</field>
<field name="model">hr.leave.type</field>
@@ -98,38 +92,24 @@
<record id="view_holiday_status_normal_tree" model="ir.ui.view">
<field name="name">hr.leave.type.normal.tree</field>
<field name="model">hr.leave.type</field>
<field name="priority">35</field>
<field name="arch" type="xml">
<tree string="Leave Type">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="limit"/>
<field name="validation_type" />
<field name="validity_start" />
<field name="validity_stop" />
<field name="allocation_type"/>
<field name="validation_type"/>
<field name="validity_start"/>
<field name="validity_stop"/>
</tree>
</field>
</record>
<record id="view_hr_holidays_status_search" model="ir.ui.view">
<field name="name">hr.leave.type.search</field>
<field name="model">hr.leave.type</field>
<field name="arch" type="xml">
<search string="Leave Type">
<field name="name" string="Leave Types"/>
<field name="categ_id"/>
</search>
</field>
</record>
<record id="open_view_holiday_status" model="ir.actions.act_window">
<field name="name">Leave Types</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="view_id" eval="view_holiday_status_normal_tree"/>
<field name="search_view_id" ref="view_hr_holidays_status_search"/>
</record>
<menuitem
+348 -206
View File
@@ -1,17 +1,16 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<!-- Graph report -->
<record id="view_evaluation_report_graph" model="ir.ui.view">
<field name="name">hr.holidays.graph</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<field name="name">hr.holidays.graph</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<graph string="Appraisal Analysis" stacked="True">
<field name="employee_id" type="row"/>
<field name="holiday_status_id" type="row"/>
<field name="date_from" type="col"/>
<field name="number_of_days" type="measure"/>
</graph>
</graph>
</field>
</record>
@@ -65,10 +64,80 @@
</field>
</record>
<!-- Holidays: Leave Request -->
<record id="hr_leave_view_kanban" model="ir.ui.view">
<field name="name">hr.leave.view.kanban</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile" create="0">
<field name="employee_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="name"/>
<field name="number_of_days"/>
<field name="can_approve"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div>
<span>
<img t-att-src="kanban_image('hr.employee', 'image_small', record.employee_id.raw_value)" t-att-title="record.employee_id.value" t-att-alt="record.employee_id.value" width="40" height="40" class="oe_kanban_avatar float-left mr4"/>
</span>
<div class="row">
<div class="col-8">
<strong class="o_kanban_record_title"><t t-esc="record.employee_id.value"/></strong>
</div>
<div class="col-4">
<span class="float-right badge badge-secondary">
<field name="state" widget="kanban_label_selection"
options="{'classes': {'draft': 'default', 'confirm': 'default', 'validate': 'success', 'cancel': 'danger'}}"/>
</span>
</div>
</div>
<div class="row">
<div class="col-12 text-muted o_kanban_record_subtitle">
<t t-esc="record.name.value"/>
</div>
</div>
<div class="o_dropdown_kanban dropdown" groups="base.group_user">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit Task</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<a t-if="record.can_approve.value" name="action_approve" type="object"
role="menuitem" class="dropdown-item">Approve</a>
<a t-if="record.can_approve.value" name="action_refuse" type="object"
role="menuitem" class="dropdown-item">Refuse</a>
</div>
</div>
</div>
<div class="o_kanban_record_bottom mt8 mb4">
<div t-attf-class="oe_kanban_bottom_left #{record.date_from.value ? 'mt8 mb4': ''}">
<table class="text-right" t-if="record.date_from.value">
<tr>
<td style="padding-bottom:4px"><small class="text-muted">from</small></td>
<td style="padding:0 0 4px 4px"><t t-esc="record.date_from.value"/></td>
</tr>
<tr>
<td><small class="text-muted">to</small></td>
<td style="padding-left:4px"><t t-esc="record.date_to.value"/></td>
</tr>
</table>
</div>
<div t-attf-class="oe_kanban_bottom_right #{record.date_from.value ? 'mt8': ''}">
<span class="badge badge-pill"><t t-esc="record.number_of_days.value"/> days</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_holiday_new_calendar" model="ir.ui.view">
<field name="name">Leave Request</field>
<record id="hr_leave_view_calendar" model="ir.ui.view">
<field name="name">hr.leave.view.calendar</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<calendar string="Leave Request" date_start="date_from" date_stop="date_to" mode="month" quick_add="False" color="employee_id">
@@ -77,9 +146,10 @@
</field>
</record>
<record id="edit_holiday_new_request" model="ir.ui.view">
<field name="name">Leave Request</field>
<record id="hr_leave_view_form" model="ir.ui.view">
<field name="name">hr.leave.view.form</field>
<field name="model">hr.leave</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<form string="Leave Request">
<field name="can_reset" invisible="1"/>
@@ -91,60 +161,94 @@
<button string="Refuse" name="action_refuse" type="object" attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', 'not in', ('confirm','validate','validate1'))]}"/>
<button string="Reset to Draft" name="action_draft" type="object"
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate"/>
<field name="state" widget="statusbar" statusbar_visible="confirm,validate"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="display_name"/></h1>
</div>
<field name="leave_type_request_unit" invisible="1"/>
<group>
<group>
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id, 'default_date_from':date_from}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id, 'default_date_from':date_from}" options="{'no_create': True, 'no_open': True}"/>
<label for="number_of_days_temp" string="Duration"/>
<field name="number_of_days" invisible="1"/>
<label for="number_of_days_display" string="Duration"/>
<div>
<div attrs="{'invisible': [('leave_type_request_unit', '=', 'hour')]}">
<field name="number_of_days_temp" nolabel="1" class="oe_inline"/>
<field name="number_of_days_display" nolabel="1" class="oe_inline"/>
<span class="ml8">Days</span>
</div>
<div attrs="{'invisible': [('leave_type_request_unit', '!=', 'hour')]}">
<field name="number_of_hours" nolabel="1" class="oe_inline"/>
<field name="number_of_hours_display" nolabel="1" class="oe_inline"/>
<span class="ml8">Hours</span>
</div>
<field name="request_unit_all" widget="radio" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '=', 'day')]}"/>
<field name="request_unit_day" widget="radio" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '!=', 'day')]}"/>
</div>
<div class="o_row">
<span attrs="{'invisible': [('request_unit_all', '!=', 'period')]}">From </span>
<field name="request_date_from" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '=', 'hour'), ('request_unit_all', '=', 'period')]}"/>
<field name="date_from" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'hour'), ('request_unit_all', '!=', 'period')]}"/>
<span class="ml8" attrs="{'invisible': ['|', '&amp;', ('request_unit_all', '!=', 'half'), ('leave_type_request_unit', '!=', 'half'), '&amp;', ('request_unit_all', '=', 'day'), ('leave_type_request_unit', '=', 'half')]}"/>
<field name="request_date_from_period" options="{'horizontal': True}"
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', '|', ('leave_type_request_unit', '=', 'day'), '&amp;', ('leave_type_request_unit', '=', 'half'), ('request_unit_all', '=', 'day'), '&amp;', ('leave_type_request_unit', '=', 'hour'), ('request_unit_all', '!=', 'half')]}"/>
<label for="request_date_from" string="From"/>
<div>
<field name="date_from" invisible="1"/>
<field name="date_to" invisible="1"/>
<div class="o_row o_row_readonly">
<field name="request_date_from" class="oe_inline"
attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}"/>
<span class="oe_inline"
attrs="{'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)]}">
To
</span>
<field name="request_date_to" class="oe_inline"
attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)]
}"/>
<field name="request_date_from_period" class="oe_inline"
string="In"
options="{'horizontal': True}"
attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'required': [('request_unit_half', '=', True)],
'invisible': [('request_unit_half', '=', False)]}"/>
</div>
<div class="o_row">
<span attrs="{'invisible': [('request_unit_all', '!=', 'period')]}">To </span>
<field name="request_date_to" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('request_unit_all', '!=', 'period'), ('leave_type_request_unit', '=', 'hour')]}"/>
<field name="date_to" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'hour'), ('request_unit_all', '!=', 'period')], 'required': ['|', ('request_unit_all', '=', 'period'), ('request_unit_day', '=', 'period')]}"/>
<span class="ml8" attrs="{'invisible': ['|', ('leave_type_request_unit', '!=', 'half'), ('request_unit_all', '!=', 'period')]}"/>
<field name="request_date_to_period" options="{'horizontal': True}"
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'half'), ('request_unit_all', '!=', 'period')]}"/>
<div class="o_row o_row_readonly">
<label for="request_unit_half"
attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}"/>
<field name="request_unit_half"
attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}"/>
<label for="request_unit_hours" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '!=', 'hour')]
}"/>
<field name="request_unit_hours" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '!=', 'hour')]
}"/>
<label for="request_unit_custom" invisible="1" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
}"/>
<field name="request_unit_custom" invisible="1" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
}"/>
</div>
<div class="o_row o_row_readonly">
<label for="request_hour_from" string="From"
attrs="{'invisible': [('request_unit_hours', '=', False)]}"/>
<field name="request_hour_from"
attrs="{
'readonly': [('state', '=', 'validate')],
'required': [('request_unit_hours', '=', True)],
'invisible': [('request_unit_hours', '=', False)]}"/>
<label for="request_hour_to" string="To"
attrs="{'invisible': [('request_unit_hours', '=', False)]}"/>
<field name="request_hour_to"
attrs="{
'readonly': [('state', '=', 'validate')],
'required': [('request_unit_hours', '=', True)],
'invisible': [('request_unit_hours', '=', False)]}"/>
</div>
</div>
<field name="name" attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}"/>
</group>
<group>
<field name="holiday_type" attrs="{'readonly':[('state','!=','draft')]}" string="Mode" groups="hr_holidays.group_hr_holidays_user" context="{'employee_id':employee_id}" />
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','!=','employee')]}" groups="hr_holidays.group_hr_holidays_user"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'readonly': [('state','!=','draft'), ('state','!=','confirm')], 'invisible':[('holiday_type','!=','category')]}"/>
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" attrs="{'readonly':['|',('state','=', 'validate'),('holiday_type','!=','department')],'invisible':[('holiday_type','=','category')]}"/>
<field name="payslip_status" groups="hr_holidays.group_hr_holidays_manager" widget="toggle_button"/>
<group name="col_right">
<field name="employee_id" invisible="1"/>
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" invisible="1"/>
</group>
<div groups="hr_holidays.group_hr_holidays_manager">
<separator string="Comment by Manager"/>
<field name="report_note" placeholder="e.g. Report to the next month..."/>
</div>
</group>
</sheet>
<div class="oe_chatter">
@@ -156,8 +260,45 @@
</field>
</record>
<record id="view_holiday" model="ir.ui.view">
<field name="name">hr.holidays.tree</field>
<record id="hr_leave_view_form_manager" model="ir.ui.view">
<field name="name">hr.leave.view.form.manager</field>
<field name="model">hr.leave</field>
<field name="inherit_id" ref="hr_leave_view_form"/>
<field name="mode">primary</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<xpath expr="//group[@name='col_right']" position="replace">
<group>
<field name="holiday_type" string="Mode"
groups="hr_holidays.group_hr_holidays_user"/>
<field name="employee_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'employee')],
'invisible': [('holiday_type', '!=', 'employee')]
}"/>
<field name="mode_company_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'company')],
'invisible': [('holiday_type', '!=', 'company')]
}"/>
<field name="category_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'category')],
'invisible': [('holiday_type', '!=','category')]
}"/>
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'department')],
'invisible': [('holiday_type', 'not in', ('employee', 'department'))]
}"/>
<field name="payslip_status" groups="hr_holidays.group_hr_holidays_manager" widget="toggle_button"/>
</group>
<div groups="hr_holidays.group_hr_holidays_manager">
<separator string="Comment by Manager"/>
<field name="report_note" placeholder="e.g. Report to the next month..."/>
</div>
</xpath>
</field>
</record>
<record id="hr_leave_view_tree" model="ir.ui.view">
<field name="name">hr.holidays.view.tree</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<tree decoration-danger="state == 'refuse'" decoration-info="state == ' draft'" string="Leave Requests"
@@ -172,14 +313,51 @@
<field name="state"/>
<field name="payslip_status" widget="toggle_button" options='{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}' groups="hr_holidays.group_hr_holidays_manager"/>
<field name="category_id" invisible="1"/>
<field name="department_id" invisible="not context.get('set_visible',False)"/>
<field name="department_id" invisible="1"/>
<field name="manager_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="message_needaction" invisible="1"/>
<button string="Approve" name="action_approve" type="object"
icon="fa-check text-success"
states="confirm"
groups="hr_holidays.group_hr_holidays_user"/>
<button string="Validate" name="action_validate" type="object"
icon="fa-check text-success"
states="validate1"
groups="hr_holidays.group_hr_holidays_manager"/>
<button string="Refuse" name="action_refuse" type="object"
icon="fa-ban text-danger"
states="confirm,validate,validate1"
groups="hr_holidays.group_hr_holidays_manager"/>
</tree>
</field>
</record>
<record id="hr_leave_view_tree_my" model="ir.ui.view">
<field name="name">hr.holidays.view.tree</field>
<field name="model">hr.leave</field>
<field name="inherit_id" ref="hr_leave_view_tree"/>
<field name="mode">primary</field>
<field name="priority">32</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='employee_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='payslip_status']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_approve']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_validate']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_refuse']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
<record id="action_hr_holidays_dashboard" model="ir.actions.act_window">
<field name="name">All Leaves</field>
<field name="res_model">hr.leave</field>
@@ -217,6 +395,24 @@
parent="menu_hr_holidays_root"
sequence="4"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"/>
<menuitem
id="hr_holidays_menu_manager_approve"
name="To Approve"
parent="menu_hr_holidays_approvals"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
sequence="1"/>
<menuitem
id="hr_holidays_menu_manager_all"
name="All"
parent="menu_hr_holidays_approvals"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
sequence="2"/>
<menuitem
id="hr_holidays_menu_manager_payroll"
name="Payroll"
parent="menu_hr_holidays_approvals"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
sequence="3"/>
<menuitem
id="menu_hr_holidays_report"
@@ -225,15 +421,13 @@
sequence="99"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"/>
<record id="open_ask_holidays" model="ir.actions.act_window">
<record id="hr_leave_action_new_request" model="ir.actions.act_window">
<field name="name">New Request</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">calendar,tree,form</field>
<field name="view_id" ref="edit_holiday_new_request"/>
<field name="context">{}</field>
<field name="domain">[('user_id', '=', uid)]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
@@ -245,104 +439,39 @@
</p>
</field>
</record>
<record id="action_open_ask_holidays_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="20"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_holiday"/>
<field name="act_window_id" ref="open_ask_holidays"/>
</record>
<record id="action_open_ask_holidays_form" model="ir.actions.act_window.view">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_holiday_new_request"/>
<field name="act_window_id" ref="open_ask_holidays"/>
</record>
<record id="action_open_ask_holidays_calendar" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<record id="hr_leave_action_new_request_view_calendar" model="ir.actions.act_window.view">
<field name="sequence">1</field>
<field name="view_mode">calendar</field>
<field name="view_id" ref="view_holiday_new_calendar"/>
<field name="act_window_id" ref="open_ask_holidays"/>
<field name="act_window_id" ref="hr_leave_action_new_request"/>
<field name="view_id" ref="hr_leave_view_calendar"/>
</record>
<record id="hr_leave_action_my_tree" model="ir.actions.act_window">
<field name="name">Leaves Request</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form</field>
<field name="view_id" ref="edit_holiday_new_request"/>
<field name="context">{}</field>
<field name="domain">[('user_id', '=', uid)]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
</p><p>
Once you have recorded your leave request, it will be sent
to a manager for validation. Be sure to set the right leave
type (recuperation, legal leaves, sickness) and the exact
number of open days related to your leave.
</p>
</field>
<record id="hr_leave_action_new_request_view_tree" model="ir.actions.act_window.view">
<field name="sequence">2</field>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="hr_leave_action_new_request"/>
<field name="view_id" ref="hr_leave_view_tree_my"/>
</record>
<record id="hhr_leave_action_new_request_view_form" model="ir.actions.act_window.view">
<field name="sequence">3</field>
<field name="view_mode">form</field>
<field name="act_window_id" ref="hr_leave_action_new_request"/>
<field name="view_id" ref="hr_leave_view_form"/>
</record>
<menuitem
id="menu_open_ask_holidays_new"
parent="menu_hr_holidays_root"
action="open_ask_holidays"
id="hr_leave_menu_new_request"
parent="menu_hr_holidays_my_leaves"
action="hr_leave_action_new_request"
sequence="2"/>
<record id="hr_leave_action_view_my_tree_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_holiday"/>
<field name="act_window_id" ref="hr_leave_action_my_tree"/>
</record>
<record id="hr_leave_action_view_my_tree_form" model="ir.actions.act_window.view">
<field name="sequence" eval="20"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_holiday_new_request"/>
<field name="act_window_id" ref="hr_leave_action_my_tree"/>
</record>
<record id="hr_leave_action_view_my_tree_calendar" model="ir.actions.act_window.view">
<field name="sequence" eval="10"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="view_holiday_new_calendar"/>
<field name="act_window_id" ref="hr_leave_action_my_tree"/>
</record>
<menuitem
id="hr_leave_menu_my"
parent="menu_hr_holidays_my_leaves"
action="hr_leave_action_my_tree"
sequence="22"/>
<record id="open_employee_leaves" model="ir.actions.act_window">
<field name="name">Leaves Overview</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="view_id" eval="view_holiday"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
</p>
</field>
<field name="context">{}</field>
<field name="domain">[]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="open_department_holidays_approve" model="ir.actions.act_window">
<field name="name">Department Leaves</field>
<record id="hr_leave_action_my" model="ir.actions.act_window">
<field name="name">Leaves Requests</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,kanban</field>
<field name="context">{'search_default_department':1, 'search_default_approve':1}</field>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="domain">[('user_id', '=', uid)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
@@ -353,90 +482,112 @@
number of open days related to your leave.
</p>
</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="hr_leave_action_my_view_tree" model="ir.actions.act_window.view">
<field name="sequence">1</field>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="hr_leave_action_my"/>
<field name="view_id" ref="hr_leave_view_tree_my"/>
</record>
<record id="hr_leave_action_my_view_form" model="ir.actions.act_window.view">
<field name="sequence">2</field>
<field name="view_mode">form</field>
<field name="act_window_id" ref="hr_leave_action_my"/>
<field name="view_id" ref="hr_leave_view_form"/>
</record>
<menuitem
id="menu_open_department_leave_approve"
name="Leaves to Approve"
parent="menu_hr_holidays_approvals"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="open_department_holidays_approve"
sequence="1"/>
<menuitem
id="menu_open_employee_leave"
parent="menu_hr_holidays_approvals"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="open_employee_leaves"
id="hr_leave_menu_my"
parent="menu_hr_holidays_my_leaves"
action="hr_leave_action_my"
sequence="4"/>
<!-- Shortcuts -->
<record id="view_allocation_tree_customize" model="ir.ui.view">
<field name="name">hr.leave.allocation.tree.customize</field>
<field name="model">hr.leave.allocation</field>
<field name="arch" type="xml">
<tree string="Allocation Requests" editable="top">
<field name="employee_id"/>
<field name="holiday_type"/>
<field name="holiday_status_id"/>
<field name="name" readonly="1"/>
<field name="number_of_days_temp" string="Allocated Days" sum="Total leaves"/>
<field name="state"/>
</tree>
<record id="hr_leave_action_all" model="ir.actions.act_window">
<field name="name">Leaves</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="context">{}</field>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
</p>
</field>
</record>
<record id="act_hr_employee_holiday_request_approved" model="ir.actions.act_window">
<field name="name">Leaves to be reported in Payslip</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave</field>
<field name="src_model">hr.employee</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_employee_id': [active_id], 'search_default_validated': True, 'search_default_gray': True}</field>
<field name="domain">[('date_from','&gt;=', context_today().strftime("%Y-%m-1")), ('date_from','&lt;', ((context_today() + relativedelta(months=1)).strftime('%Y-%m-1')) )]</field>
<field name="view_id" eval="view_allocation_tree_customize"/>
</record>
<!-- Assing leave -->
<record id="hr_holidays_action_request_from_department" model="ir.actions.act_window">
<field name="name">Leave Request to Approve</field>
<record id="hr_leave_action_action_approve_department" model="ir.actions.act_window">
<field name="name">Department Leaves</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="context">{
'search_default_department_id': [active_id],
'default_department_id': active_id,
'search_default_department':1,
'search_default_approve':1}
</field>
<field name="domain">[]</field>
<field name="view_id" ref="view_holiday"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
</p>
</field>
</record>
<record id="action_hr_holidays_leaves_analysis" model="ir.actions.act_window">
<record id="hr_leave_action_action_department" model="ir.actions.act_window">
<field name="name">Leaves Analysis</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">graph,pivot</field>
<field name="domain">[('holiday_type','=','employee')]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="action_hr_holidays_leaves_analysis_filtered" model="ir.actions.act_window">
<field name="name">Leaves Analysis</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">graph,pivot</field>
<field name="domain">[('holiday_type','=','employee')]</field>
<field name="context">{
'search_default_department_id': [active_id],
'default_department_id': active_id}
</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
<field name="domain">[('holiday_type','=','employee')]</field>
</record>
<menuitem
id="menu_open_department_leave_approve"
name="Leaves"
parent="hr_holidays_menu_manager_approve"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="hr_leave_action_action_approve_department"
sequence="1"/>
<menuitem
id="menu_open_employee_leave"
name="Leaves"
parent="hr_holidays_menu_manager_all"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="hr_leave_action_all"
sequence="1"/>
<record id="hr_leave_action_payroll" model="ir.actions.act_window">
<field name="name">Leaves Requests</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="context">{'search_default_gray': True}</field>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new leave request
</p><p>
Once you have recorded your leave request, it will be sent
to a manager for validation. Be sure to set the right leave
type (recuperation, legal leaves, sickness) and the exact
number of open days related to your leave.
</p>
</field>
</record>
<menuitem
id="hr_holidays_menu_manager_payroll_to_report"
name="Leaves to report"
parent="hr_holidays_menu_manager_payroll"
groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
action="hr_leave_action_payroll"
sequence="1"/>
<record id="view_holiday_pivot" model="ir.ui.view">
<field name="name">hr.holidays.report_pivot</field>
<field name="model">hr.leave</field>
@@ -467,16 +618,7 @@
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">graph,pivot,calendar,form</field>
<field name="view_id"></field> <!-- force empty -->
<field name="context">{'search_default_leaves':1}</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="action_window_leave_pivot" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">pivot</field>
<field name="view_id" ref="view_holiday_pivot"/>
<field name="act_window_id" ref="action_hr_available_holidays_report"/>
</record>
<record id="action_window_leave_graph" model="ir.actions.act_window.view">
+5 -45
View File
@@ -1,46 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<odoo>
<record id="act_hr_employee_holiday_request" model="ir.actions.act_window">
<field name="name">Leaves</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave.report</field>
<field name="src_model">hr.employee</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id, 'search_default_group_type': 1,
'search_default_year': 1}</field>
<field name="domain">[('holiday_type','=','employee'), ('holiday_status_id.limit', '=', False), ('state','!=', 'refuse')]</field>
<field name="search_view_id" ref="view_hr_holidays_filter_report"/>
</record>
<record id="hr_holidays_leaves_assign_tree_view" model="ir.ui.view">
<field name="name">hr.employee.leave.tree</field>
<field name="model">hr.employee</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<tree string="Assign Leaves" editable="bottom">
<field name="name" string="Employee"/>
<field name="remaining_leaves" string="Remaining Legal Leaves"/>
</tree>
</field>
</record>
<record id="hr_holidays_leaves_assign_legal" model="ir.actions.act_window">
<field name="name">Allocate Leaves for Employees</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.employee</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="hr_holidays_leaves_assign_tree_view"/>
<field name="help" type="html">
<p>
You can assign remaining Legal Leaves for each employee, Odoo
will automatically create and validate allocation requests.
</p>
</field>
</record>
<record id="hr_employee_action_from_department" model="ir.actions.act_window">
<field name="name">Absent Employees</field>
<field name="res_model">hr.employee</field>
@@ -72,7 +32,7 @@
<xpath expr="//div[hasclass('o_kanban_primary_right')]" position="inside">
<div t-if="record.leave_to_approve_count.raw_value > 0" class="row">
<div class="col-9">
<a name="%(hr_holidays_action_request_from_department)d" type="action">
<a name="%(hr_leave_action_action_approve_department)d" type="action">
Leave Requests
</a>
</div>
@@ -82,7 +42,7 @@
</div>
<div t-if="record.allocation_to_approve_count.raw_value > 0" class="row">
<div class="col-9">
<a name="%(hr_holidays_action_allocation_from_department)d" type="action">
<a name="%(hr_leave_allocation_action_approve_department)d" type="action">
Allocation Requests
</a>
</div>
@@ -108,7 +68,7 @@
<xpath expr="//div[hasclass('o_kanban_manage_to_approve')]" position="inside">
<div class="row">
<div class="col-12 text-left">
<a name="%(hr_holidays_action_request_from_department)d" type="action">
<a name="%(hr_leave_action_action_approve_department)d" type="action">
<t t-esc="record.leave_to_approve_count.raw_value or 0"/> Leave Requests
</a>
</div>
@@ -116,7 +76,7 @@
<div class="row">
<div class="col-12 text-left">
<a name="%(hr_holidays_action_allocation_from_department)d" type="action">
<a name="%(hr_leave_allocation_action_approve_department)d" type="action">
<t t-esc="record.allocation_to_approve_count.raw_value or 0"/> Allocation Requests
</a>
</div>
@@ -126,7 +86,7 @@
<xpath expr="//div[hasclass('o_kanban_manage_reports')]" position="inside">
<div class="row">
<div class="col-12 text-left">
<a name="%(action_hr_holidays_leaves_analysis_filtered)d" type="action">
<a name="%(hr_leave_action_action_department)d" type="action">
Leaves
</a>
</div>
@@ -29,7 +29,7 @@
</record>
<menuitem
name="Leaves by Department"
name="Report by Department"
parent="menu_hr_holidays_report"
action="action_hr_holidays_summary_dept"
id="menu_hr_holidays_summary_dept"/>
@@ -16,7 +16,7 @@ class TestTimesheetHolidaysCreate(common.TransactionCase):
"""Ensure that when a status is created, it fullfills the project and task constrains"""
status = self.env['hr.leave.type'].create({
'name': 'A nice Leave Type',
'limit': True
'allocation_type': 'no'
})
company = self.env.user.company_id
@@ -42,14 +42,14 @@ class TestTimesheetHolidays(TestTimesheet):
self.hr_leave_type_with_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type with timesheet generation',
'limit': True,
'allocation_type': 'no',
'timesheet_generate': True,
'timesheet_project_id': self.internal_project.id,
'timesheet_task_id': self.internal_task_leaves.id,
})
self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type without timesheet generation',
'limit': True,
'allocation_type': 'no',
'timesheet_generate': False,
'timesheet_project_id': False,
'timesheet_task_id': False,
@@ -62,14 +62,14 @@ class TestTimesheetHolidays(TestTimesheet):
'name': 'Days for limited category with timesheet',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'number_of_days_temp': 10,
'number_of_days': 10,
})
self.hr_leave_allocation_with_ts.action_approve()
self.hr_leave_allocation_no_ts = self.Allocations.sudo().create({
'name': 'Days for limited category without timesheet',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_no_ts.id,
'number_of_days_temp': 10,
'number_of_days': 10,
})
self.hr_leave_allocation_no_ts.action_approve()
@@ -82,7 +82,7 @@ class TestTimesheetHolidays(TestTimesheet):
'holiday_status_id': self.hr_leave_type_with_ts.id,
'date_from': self.leave_start_datetime,
'date_to': self.leave_end_datetime,
'number_of_days_temp': number_of_days,
'number_of_days': number_of_days,
})
holiday.sudo().action_validate()
self.assertEquals(len(holiday.timesheet_ids), number_of_days, 'Number of generated timesheets should be the same as the leave duration (1 per day between %s and %s)' % (fields.Datetime.to_string(self.leave_start_datetime), fields.Datetime.to_string(self.leave_end_datetime)))
@@ -100,7 +100,7 @@ class TestTimesheetHolidays(TestTimesheet):
'holiday_status_id': self.hr_leave_type_no_ts.id,
'date_from': self.leave_start_datetime,
'date_to': self.leave_end_datetime,
'number_of_days_temp': number_of_days,
'number_of_days': number_of_days,
})
holiday.sudo().action_validate()
self.assertEquals(len(holiday.timesheet_ids), 0, 'Number of generated timesheets should be zero since the leave type does not generate timesheet')
@@ -6,12 +6,10 @@
<field name="model">hr.leave.type</field>
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='description']" position="after">
<group name="timesheet" string="Timesheet">
<field name="timesheet_generate"/>
<field name="timesheet_project_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
<field name="timesheet_task_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
</group>
<xpath expr="//field[@name='unpaid']" position="after">
<field name="timesheet_generate"/>
<field name="timesheet_project_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
<field name="timesheet_task_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
</xpath>
</field>
</record>
@@ -125,10 +125,10 @@ var CalendarView = AbstractView.extend({
attrs.form_view_id ? parseInt(attrs.form_view_id, 10) : false;
if (!this.controllerParams.formViewId && params.action) {
var formViewDescr = _.find(params.action.views, function (v) {
return v[1] === 'form';
return v.type === 'form';
});
if (formViewDescr) {
this.controllerParams.formViewId = formViewDescr[0];
this.controllerParams.formViewId = formViewDescr.viewID;
}
}
@@ -2248,7 +2248,7 @@ QUnit.module('Views', {
archs: archs,
viewOptions: {
initialDate: initialDate,
action: {views: [[1, 'kanban'], [43, 'form']]}
action: {views: [{viewID: 1, type: 'kanban'}, {viewID: 43, type: 'form'}]}
},
mockRPC: function (route, args) {
if (args.method === "create") {