[REF] resource: move code from mixin to calendar

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
This commit is contained in:
jem-odoo
2019-08-13 12:16:26 +00:00
parent f9fafcdcfb
commit f499166df0
2 changed files with 74 additions and 46 deletions
+70 -1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
import math
from datetime import datetime, time, timedelta
from dateutil.relativedelta import relativedelta
@@ -14,12 +15,15 @@ from odoo.addons.base.models.res_partner import _tz_get
from odoo.exceptions import ValidationError
from odoo.osv import expression
from odoo.tools.float_utils import float_round
from odoo.tools import date_utils
from odoo.tools import date_utils, float_utils
# Default hour per day value. The one should
# only be used when the one from the calendar
# is not available.
HOURS_PER_DAY = 8
# This will generate 16th of days
ROUNDING_FACTOR = 16
def make_aware(dt):
@@ -396,6 +400,45 @@ class ResourceCalendar(models.Model):
return (self._attendance_intervals(start_dt, end_dt, resource) -
self._leave_intervals(start_dt, end_dt, resource, domain))
# --------------------------------------------------
# Private Methods / Helpers
# --------------------------------------------------
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, resource=None):
"""
@return dict with hours of attendance in each day between `from_datetime` and `to_datetime`
"""
self.ensure_one()
# 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 = self._attendance_intervals(from_full, to_full, resource=resource)
day_total = defaultdict(float)
for start, stop, meta in intervals:
day_total[start.date()] += (stop - start).total_seconds() / 3600
return day_total
# --------------------------------------------------
# External API
# --------------------------------------------------
@@ -426,6 +469,32 @@ class ResourceCalendar(models.Model):
for start, stop, meta in intervals
)
def get_work_duration_data(self, from_datetime, to_datetime, compute_leaves=True, domain=None):
"""
Get the working duration (in days and hours) for a given period, only
based on the current calendar. This method does not use resource to
compute it.
`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.
"""
# naive datetimes are made explicit in UTC
from_datetime, dummy = make_aware(from_datetime)
to_datetime, dummy = make_aware(to_datetime)
day_total = self._get_day_total(from_datetime, to_datetime)
# actual hours per day
if compute_leaves:
intervals = self._work_intervals(from_datetime, to_datetime, domain=domain)
else:
intervals = self._attendance_intervals(from_datetime, to_datetime)
return self._get_days_data(intervals, day_total)
def plan_hours(self, hours, day_dt, compute_leaves=False, domain=None, resource=None):
"""
`compute_leaves` controls whether or not this method is taking into
+4 -45
View File
@@ -2,14 +2,9 @@
# 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
def timezone_datetime(time):
@@ -58,41 +53,6 @@ class ResourceMixin(models.AbstractModel):
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 _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
@@ -111,7 +71,7 @@ class ResourceMixin(models.AbstractModel):
from_datetime = timezone_datetime(from_datetime)
to_datetime = timezone_datetime(to_datetime)
day_total = self._get_day_total(from_datetime, to_datetime, calendar, resource)
day_total = calendar._get_day_total(from_datetime, to_datetime, resource)
# actual hours per day
if compute_leaves:
@@ -119,7 +79,7 @@ class ResourceMixin(models.AbstractModel):
else:
intervals = calendar._attendance_intervals(from_datetime, to_datetime, resource)
return self._get_days_data(intervals, day_total)
return calendar._get_days_data(intervals, day_total)
def _get_leave_days_data(self, from_datetime, to_datetime, calendar=None, domain=None):
"""
@@ -139,14 +99,13 @@ class ResourceMixin(models.AbstractModel):
from_datetime = timezone_datetime(from_datetime)
to_datetime = timezone_datetime(to_datetime)
day_total = self._get_day_total(from_datetime, to_datetime, calendar, resource)
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 self._get_days_data(attendances & leaves, day_total)
return calendar._get_days_data(attendances & leaves, day_total)
def list_work_time_per_day(self, from_datetime, to_datetime, calendar=None, domain=None):
"""