We would like to be able to take leaves using different units, currently we only support taking leaves by hours because we use datetimes. We need to support three units: * days * half days * hours These units will be defined on the leave type. Task #40995 Closes #21760
203 lines
8.4 KiB
Python
203 lines
8.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import defaultdict
|
|
from datetime import timedelta
|
|
from pytz import utc
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.tools import float_utils
|
|
|
|
# This will generate eights of days
|
|
ROUNDING_FACTOR = 8
|
|
|
|
|
|
class ResourceMixin(models.AbstractModel):
|
|
_name = "resource.mixin"
|
|
_description = 'Resource Mixin'
|
|
|
|
resource_id = fields.Many2one(
|
|
'resource.resource', 'Resource',
|
|
auto_join=True, index=True, ondelete='restrict', required=True)
|
|
company_id = fields.Many2one(
|
|
'res.company', 'Company',
|
|
default=lambda self: self.env['res.company']._company_default_get(),
|
|
index=True, related='resource_id.company_id', store=True)
|
|
resource_calendar_id = fields.Many2one(
|
|
'resource.calendar', 'Working Hours',
|
|
default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id,
|
|
index=True, related='resource_id.calendar_id', store=True)
|
|
tz = fields.Selection(
|
|
string='Timezone', related='resource_id.tz',
|
|
help="This field is used in order to define in which timezone the resources will work.")
|
|
|
|
@api.model
|
|
def create(self, values):
|
|
if not values.get('resource_id'):
|
|
resource_vals = {'name': values.get(self._rec_name)}
|
|
tz = (values.pop('tz', False) or
|
|
self.env['resource.calendar'].browse(values.get('resource_calendar_id')).tz)
|
|
if tz:
|
|
resource_vals['tz'] = tz
|
|
resource = self.env['resource.resource'].create(resource_vals)
|
|
values['resource_id'] = resource.id
|
|
return super(ResourceMixin, self).create(values)
|
|
|
|
@api.multi
|
|
def copy_data(self, default=None):
|
|
if default is None:
|
|
default = {}
|
|
resource = self.resource_id.copy()
|
|
default['resource_id'] = resource.id
|
|
default['company_id'] = resource.company_id.id
|
|
default['resource_calendar_id'] = resource.calendar_id.id
|
|
return super(ResourceMixin, self).copy_data(default)
|
|
|
|
def get_work_days_data(self, from_datetime, to_datetime, compute_leaves=True, calendar=None, domain=None):
|
|
"""
|
|
By default the resource calendar is used, but it can be
|
|
changed using the `calendar` argument.
|
|
|
|
`domain` is used in order to recognise the leaves to take,
|
|
None means default value ('time_type', '=', 'leave')
|
|
|
|
Returns a dict {'days': n, 'hours': h} containing the
|
|
quantity of working time expressed as days and as hours.
|
|
"""
|
|
resource = self.resource_id
|
|
calendar = calendar or self.resource_calendar_id
|
|
|
|
# naive datetimes are made explicit in UTC
|
|
if not from_datetime.tzinfo:
|
|
from_datetime = from_datetime.replace(tzinfo=utc)
|
|
if not to_datetime.tzinfo:
|
|
to_datetime = to_datetime.replace(tzinfo=utc)
|
|
|
|
# total hours per day: retrieve attendances with one extra day margin,
|
|
# in order to compute the total hours on the first and last days
|
|
from_full = from_datetime - timedelta(days=1)
|
|
to_full = to_datetime + timedelta(days=1)
|
|
intervals = calendar._attendance_intervals(from_full, to_full, resource)
|
|
day_total = defaultdict(float)
|
|
for start, stop, meta in intervals:
|
|
day_total[start.date()] += (stop - start).total_seconds() / 3600
|
|
|
|
# actual hours per day
|
|
if compute_leaves:
|
|
intervals = calendar._work_intervals(from_datetime, to_datetime, resource, domain)
|
|
else:
|
|
intervals = calendar._attendance_intervals(from_datetime, to_datetime, resource)
|
|
day_hours = defaultdict(float)
|
|
for start, stop, meta in intervals:
|
|
day_hours[start.date()] += (stop - start).total_seconds() / 3600
|
|
|
|
# compute number of days as quarters
|
|
days = sum(
|
|
float_utils.round(ROUNDING_FACTOR * day_hours[day] / day_total[day]) / ROUNDING_FACTOR
|
|
for day in day_hours
|
|
)
|
|
return {
|
|
'days': days,
|
|
'hours': sum(day_hours.values()),
|
|
}
|
|
|
|
def get_leave_days_data(self, from_datetime, to_datetime, calendar=None, domain=None):
|
|
"""
|
|
By default the resource calendar is used, but it can be
|
|
changed using the `calendar` argument.
|
|
|
|
`domain` is used in order to recognise the leaves to take,
|
|
None means default value ('time_type', '=', 'leave')
|
|
|
|
Returns a dict {'days': n, 'hours': h} containing the number of leaves
|
|
expressed as days and as hours.
|
|
"""
|
|
resource = self.resource_id
|
|
calendar = calendar or self.resource_calendar_id
|
|
|
|
# naive datetimes are made explicit in UTC
|
|
if not from_datetime.tzinfo:
|
|
from_datetime = from_datetime.replace(tzinfo=utc)
|
|
if not to_datetime.tzinfo:
|
|
to_datetime = to_datetime.replace(tzinfo=utc)
|
|
|
|
# total hours per day: retrieve attendances with one extra day margin,
|
|
# in order to compute the total hours on the first and last days
|
|
from_full = from_datetime - timedelta(days=1)
|
|
to_full = to_datetime + timedelta(days=1)
|
|
intervals = calendar._attendance_intervals(from_full, to_full, resource)
|
|
day_total = defaultdict(float)
|
|
for start, stop, meta in intervals:
|
|
day_total[start.date()] += (stop - start).total_seconds() / 3600
|
|
|
|
# compute actual hours per day
|
|
attendances = calendar._attendance_intervals(from_datetime, to_datetime, resource)
|
|
leaves = calendar._leave_intervals(from_datetime, to_datetime, resource, domain)
|
|
day_hours = defaultdict(float)
|
|
for start, stop, meta in (attendances & leaves):
|
|
day_hours[start.date()] += (stop - start).total_seconds() / 3600
|
|
|
|
# compute number of days as quarters
|
|
days = sum(
|
|
float_utils.round(ROUNDING_FACTOR * day_hours[day] / day_total[day]) / ROUNDING_FACTOR
|
|
for day in day_hours
|
|
)
|
|
return {
|
|
'days': days,
|
|
'hours': sum(day_hours.values()),
|
|
}
|
|
|
|
def list_work_time_per_day(self, from_datetime, to_datetime, calendar=None, domain=None):
|
|
"""
|
|
By default the resource calendar is used, but it can be
|
|
changed using the `calendar` argument.
|
|
|
|
`domain` is used in order to recognise the leaves to take,
|
|
None means default value ('time_type', '=', 'leave')
|
|
|
|
Returns a list of tuples (day, hours) for each day
|
|
containing at least an attendance.
|
|
"""
|
|
resource = self.resource_id
|
|
calendar = calendar or self.resource_calendar_id
|
|
|
|
# naive datetimes are made explicit in UTC
|
|
if not from_datetime.tzinfo:
|
|
from_datetime = from_datetime.replace(tzinfo=utc)
|
|
if not to_datetime.tzinfo:
|
|
to_datetime = to_datetime.replace(tzinfo=utc)
|
|
|
|
intervals = calendar._work_intervals(from_datetime, to_datetime, resource, domain)
|
|
result = defaultdict(float)
|
|
for start, stop, meta in intervals:
|
|
result[start.date()] += (stop - start).total_seconds() / 3600
|
|
return sorted(result.items())
|
|
|
|
def list_leaves(self, from_datetime, to_datetime, calendar=None, domain=None):
|
|
"""
|
|
By default the resource calendar is used, but it can be
|
|
changed using the `calendar` argument.
|
|
|
|
`domain` is used in order to recognise the leaves to take,
|
|
None means default value ('time_type', '=', 'leave')
|
|
|
|
Returns a list of tuples (day, hours, resource.calendar.leaves)
|
|
for each leave in the calendar.
|
|
"""
|
|
resource = self.resource_id
|
|
calendar = calendar or self.resource_calendar_id
|
|
|
|
# naive datetimes are made explicit in UTC
|
|
if not from_datetime.tzinfo:
|
|
from_datetime = from_datetime.replace(tzinfo=utc)
|
|
if not to_datetime.tzinfo:
|
|
to_datetime = to_datetime.replace(tzinfo=utc)
|
|
|
|
attendances = calendar._attendance_intervals(from_datetime, to_datetime, resource)
|
|
leaves = calendar._leave_intervals(from_datetime, to_datetime, resource, domain)
|
|
result = []
|
|
for start, stop, leave in (leaves & attendances):
|
|
hours = (stop - start).total_seconds() / 3600
|
|
result.append((start.date(), hours, leave))
|
|
return result
|