[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
This commit is contained in:
lul-odoo
2018-11-30 11:08:19 +01:00
committed by Yannick Tivisse
parent 07bec7db87
commit 5a9b8a41b6
2 changed files with 60 additions and 50 deletions
+10 -2
View File
@@ -197,13 +197,20 @@ class ResourceCalendar(models.Model):
# --------------------------------------------------
# Computation API
# --------------------------------------------------
def _attendance_intervals(self, start_dt, end_dt, resource=None):
def _attendance_intervals(self, start_dt, end_dt, resource=None, domain=None):
""" Return the attendance intervals in the given datetime range.
The returned intervals are expressed in the resource's timezone.
"""
assert start_dt.tzinfo and end_dt.tzinfo
combine = datetime.combine
resource_ids = [resource.id, False] if resource else [False]
domain = domain if domain is not None else []
domain = domain + [
('calendar_id', '=', self.id),
('resource_id', 'in', resource_ids),
]
# express all dates and times in the resource's timezone
tz = timezone((resource or self).tz)
start_dt = start_dt.astimezone(tz)
@@ -211,7 +218,7 @@ class ResourceCalendar(models.Model):
# for each attendance spec, generate the intervals in the date range
result = []
for attendance in self.attendance_ids:
for attendance in self.env['resource.calendar.attendance'].search(domain):
start = start_dt.date()
if attendance.date_from:
start = max(start, attendance.date_from)
@@ -404,6 +411,7 @@ class ResourceCalendarAttendance(models.Model):
hour_to = fields.Float(string='Work to', required=True)
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')
day_period = fields.Selection([('morning', 'Morning'), ('afternoon', 'Afternoon')], required=True, default='morning')
resource_id = fields.Many2one('resource.resource', 'Resource')
@api.onchange('hour_from', 'hour_to')
def _onchange_hours(self):
+50 -48
View File
@@ -53,6 +53,47 @@ 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 _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
@@ -68,38 +109,18 @@ class ResourceMixin(models.AbstractModel):
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)
from_datetime = self._timezone_datetime(from_datetime)
to_datetime = self._timezone_datetime(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
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)
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()),
}
return self._get_days_data(intervals, day_total)
def get_leave_days_data(self, from_datetime, to_datetime, calendar=None, domain=None):
"""
@@ -116,36 +137,17 @@ class ResourceMixin(models.AbstractModel):
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)
from_datetime = self._timezone_datetime(from_datetime)
to_datetime = self._timezone_datetime(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
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)
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()),
}
return self._get_days_data(attendances & leaves, day_total)
def list_work_time_per_day(self, from_datetime, to_datetime, calendar=None, domain=None):
"""