diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index a51ab643a18..20018adb968 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -6,11 +6,11 @@
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
+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 _
@@ -62,6 +62,7 @@ class HolidaysRequest(models.Model):
@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([('valid', '=', True)])
@@ -72,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'),
@@ -107,9 +128,11 @@ class HolidaysRequest(models.Model):
# 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',
@@ -143,20 +166,72 @@ class HolidaysRequest(models.Model):
help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)')
can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
- # UX FIELDS
- # used when not using hour-based computation
+
+ # 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')
+ # 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_date_to_period = fields.Selection([
- ('am', 'Morning'), ('pm', 'Afternoon')],
- string="Date Period End", default='pm')
# request type
- request_unit = fields.Selection([('day', '1 Day'),
- ('period', 'Period')], default='day')
+ 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."),
@@ -164,69 +239,70 @@ class HolidaysRequest(models.Model):
('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', '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 = 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 in ['day']:
- 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 == 'day' or (self.request_unit == '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)
- if self.request_unit == 'period' and self.leave_type_request_unit == 'hour':
- 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).replace(tzinfo=None)
- date_to = timezone(tz).localize(datetime.combine(last_day, hour_to)).astimezone(UTC).replace(tzinfo=None)
+ 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)
- self.date_from = date_from
- self.date_to = date_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 not (self.request_unit == 'period' and self.leave_type_request_unit == 'hour'):
- date_from = date_from
- date_to = date_to
+ @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.number_of_days = self._get_number_of_days(date_from, date_to, self.employee_id.id)
+ @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):
@@ -241,54 +317,18 @@ class HolidaysRequest(models.Model):
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 = 'period'
-
- # Compute and update the number of days
- if (date_to and date_from) and (date_from <= date_to):
- self.number_of_days = self._get_number_of_days(date_from, date_to, self.employee_id.id)
- else:
- self.number_of_days = 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 = 'period'
-
- # Compute and update the number of days
- if (date_to and date_from) and (date_from <= date_to):
- self.number_of_days = self._get_number_of_days(date_from, date_to, self.employee_id.id)
- else:
- self.number_of_days = 0
-
@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
+ @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')
def _compute_number_of_days_display(self):
@@ -299,7 +339,8 @@ class HolidaysRequest(models.Model):
@api.depends('number_of_days')
def _compute_number_of_hours_display(self):
for holiday in self:
- holiday.number_of_hours_display = holiday.number_of_days * 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')
@@ -485,11 +526,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 * 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,
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index 16c35241dc2..6a011c5b03e 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -27,8 +27,11 @@ class HolidaysAllocation(models.Model):
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):
- lt = self.env['hr.leave.type'].with_context(employee_id=self._default_employee().id).search([('valid', '=', True)], limit=1)
- 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([
diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py
index a5101ace28c..828df4a5586 100644
--- a/addons/hr_holidays/models/hr_leave_type.py
+++ b/addons/hr_holidays/models/hr_leave_type.py
@@ -150,13 +150,19 @@ class HolidaysType(models.Model):
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)
@@ -221,8 +227,9 @@ 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: (l.allocation_type == 'fixed', l.allocation_type == 'fixed_allocation', l.virtual_remaining_leaves)
return leaves.sorted(key=sort_key, reverse=True).ids
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml
index 895bf975e5b..fffb8724478 100644
--- a/addons/hr_holidays/views/hr_leave_views.xml
+++ b/addons/hr_holidays/views/hr_leave_views.xml
@@ -89,17 +89,13 @@
-
+
-
-
-
-
-
+
@@ -112,25 +108,70 @@
Hours
-
+
-
-
From
-
-
-
-
+
+
+
+
+
+
+
+ To
+
+
+
-
-
To
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+