Files
odoo_source/addons/resource/models/resource_mixin.py
T
lul-odoo 5a9b8a41b6 [IMP] resource: Add domain parameter to compute attendace intervals
Purpose
=======
More flexibility is useful when using the _attendance_intervals method (resource.calendar) in other modules when inheriting resource.calendar

Specification
=============
The commit adds a `domain` parameter to the _attendance_intervals method in resource.calendar, allowing
to better control attendances from which to compute intervals.

+ small refactoring: extraction of duplicated code in method
2018-11-30 11:08:19 +01:00

205 lines
8.2 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 16th of days
ROUNDING_FACTOR = 16
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, readonly=False)
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, readonly=False)
tz = fields.Selection(
string='Timezone', related='resource_id.tz', readonly=False,
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_days_data(self, intervals, day_total):
"""
helper function to compute duration of `intervals`
expressed in days and hours.
`day_total` is a dict {date: n_hours} with the number of hours for each day.
"""
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_day_total(self, from_datetime, to_datetime, calendar, resource):
"""
@return dict with hours of attendance in each day between `from_datetime` and `to_datetime`
"""
# 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
return day_total
def _timezone_datetime(self, time):
if not time.tzinfo:
time = time.replace(tzinfo=utc)
return time
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
from_datetime = self._timezone_datetime(from_datetime)
to_datetime = self._timezone_datetime(to_datetime)
day_total = self._get_day_total(from_datetime, to_datetime, calendar, resource)
# 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)
return self._get_days_data(intervals, day_total)
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
from_datetime = self._timezone_datetime(from_datetime)
to_datetime = self._timezone_datetime(to_datetime)
day_total = self._get_day_total(from_datetime, to_datetime, calendar, resource)
# compute actual hours per day
attendances = calendar._attendance_intervals(from_datetime, to_datetime, resource)
leaves = calendar._leave_intervals(from_datetime, to_datetime, resource, domain)
return self._get_days_data(attendances & leaves, day_total)
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