This commit simply moves methods from the resource mixin to the calendar, so it can be use without having a resource. Indeed, in some case, we want to know the duration (in days and hours) according to a working calendar without any resource attached (typically services using a working calendar). Task-37561
163 lines
6.6 KiB
Python
163 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import defaultdict
|
|
from pytz import utc
|
|
|
|
from odoo import api, fields, models
|
|
|
|
|
|
def timezone_datetime(time):
|
|
if not time.tzinfo:
|
|
time = time.replace(tzinfo=utc)
|
|
return time
|
|
|
|
|
|
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.company,
|
|
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.company.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)
|
|
|
|
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
|
|
from_datetime = timezone_datetime(from_datetime)
|
|
to_datetime = timezone_datetime(to_datetime)
|
|
|
|
day_total = calendar._get_day_total(from_datetime, to_datetime, 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 calendar._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 = timezone_datetime(from_datetime)
|
|
to_datetime = timezone_datetime(to_datetime)
|
|
|
|
day_total = calendar._get_day_total(from_datetime, to_datetime, 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 calendar._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
|