[REF] resource: include attendance and leave info in intervals
Calendar and resource scheduling work on time intervals. However in some cases it is useful to have info about attendances and / or leaves linked to those intervals. Internal interval tuples is thus extended to keep records about resource.calendar.attendance and resource.calendar.leaves. This will notably allow to remove some hacks in stock_calendar module. Hooray.
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
import itertools
|
||||
import pytz
|
||||
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta
|
||||
from dateutil import rrule
|
||||
from dateutil.relativedelta import relativedelta
|
||||
@@ -32,6 +34,7 @@ class ResourceCalendar(models.Model):
|
||||
tuples, holding several intervals of work or leaves. """
|
||||
_name = "resource.calendar"
|
||||
_description = "Resource Calendar"
|
||||
_interval_obj = namedtuple('Interval', ('start_datetime', 'end_datetime', 'data'))
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
@@ -72,75 +75,97 @@ class ResourceCalendar(models.Model):
|
||||
# Utility methods
|
||||
# --------------------------------------------------
|
||||
|
||||
def _interval_clean(self, intervals):
|
||||
""" Utility method that sorts and removes overlapping inside datetime
|
||||
intervals. The intervals are sorted based on increasing starting datetime.
|
||||
Overlapping intervals are merged into a single one.
|
||||
def _merge_kw(self, kw, kw_ext):
|
||||
new_kw = dict(kw, **kw_ext)
|
||||
new_kw.update(
|
||||
attendances=kw.get('attendances', self.env['resource.calendar.attendance']) | kw_ext.get('attendances', self.env['resource.calendar.attendance']),
|
||||
leaves=kw.get('leaves', self.env['resource.calendar.leaves']) | kw_ext.get('leaves', self.env['resource.calendar.leaves'])
|
||||
)
|
||||
return new_kw
|
||||
|
||||
:param list intervals: list of intervals; each interval is a tuple
|
||||
(datetime_from, datetime_to)
|
||||
:return list cleaned: list of sorted intervals without overlap """
|
||||
def _interval_new(self, start_datetime, end_datetime, kw=None):
|
||||
kw = kw if kw is not None else dict()
|
||||
kw.setdefault('attendances', self.env['resource.calendar.attendance'])
|
||||
kw.setdefault('leaves', self.env['resource.calendar.leaves'])
|
||||
return self._interval_obj(start_datetime, end_datetime, kw)
|
||||
|
||||
def _interval_exclude_left(self, interval, interval_dst):
|
||||
return self._interval_obj(
|
||||
interval.start_datetime > interval_dst.end_datetime and interval.start_datetime or interval_dst.end_datetime,
|
||||
interval.end_datetime,
|
||||
self._merge_kw(interval.data, interval_dst.data)
|
||||
)
|
||||
|
||||
def _interval_exclude_right(self, interval, interval_dst):
|
||||
return self._interval_obj(
|
||||
interval.start_datetime,
|
||||
interval.end_datetime < interval_dst.start_datetime and interval.end_datetime or interval_dst.start_datetime,
|
||||
self._merge_kw(interval.data, interval_dst.data)
|
||||
)
|
||||
|
||||
def _interval_or(self, interval, interval_dst):
|
||||
return self._interval_obj(
|
||||
interval.start_datetime < interval_dst.start_datetime and interval.start_datetime or interval_dst.start_datetime,
|
||||
interval.end_datetime > interval_dst.end_datetime and interval.end_datetime or interval_dst.end_datetime,
|
||||
self._merge_kw(interval.data, interval_dst.data)
|
||||
)
|
||||
|
||||
def _interval_and(self, interval, interval_dst):
|
||||
return self._interval_obj(
|
||||
interval.start_datetime > interval_dst.start_datetime and interval.start_datetime or interval_dst.start_datetime,
|
||||
interval.end_datetime < interval_dst.end_datetime and interval.end_datetime or interval_dst.end_datetime,
|
||||
self._merge_kw(interval.data, interval_dst.data)
|
||||
)
|
||||
|
||||
def _interval_merge(self, intervals):
|
||||
""" Sort intervals based on starting datetime and merge overlapping intervals.
|
||||
|
||||
:return list cleaned: sorted intervals merged without overlap """
|
||||
intervals = sorted(intervals, key=itemgetter(0)) # sort on first datetime
|
||||
cleaned = []
|
||||
working_interval = None
|
||||
while intervals:
|
||||
current_interval = intervals.pop(0)
|
||||
if not working_interval: # init
|
||||
working_interval = [current_interval[0], current_interval[1]]
|
||||
working_interval = self._interval_new(*current_interval)
|
||||
elif working_interval[1] < current_interval[0]: # interval is disjoint
|
||||
cleaned.append(tuple(working_interval))
|
||||
working_interval = [current_interval[0], current_interval[1]]
|
||||
cleaned.append(working_interval)
|
||||
working_interval = self._interval_new(*current_interval)
|
||||
elif working_interval[1] < current_interval[1]: # union of greater intervals
|
||||
working_interval[1] = current_interval[1]
|
||||
working_interval = self._interval_or(working_interval, current_interval)
|
||||
if working_interval: # handle void lists
|
||||
cleaned.append(tuple(working_interval))
|
||||
cleaned.append(working_interval)
|
||||
return cleaned
|
||||
|
||||
@api.model
|
||||
def _interval_remove_leaves(self, interval, leave_intervals):
|
||||
""" Utility method that remove leave intervals from a base interval:
|
||||
""" Remove leave intervals from a base interval
|
||||
|
||||
- clean the leave intervals, to have an ordered list of not-overlapping
|
||||
intervals
|
||||
- initiate the current interval to be the base interval
|
||||
- for each leave interval:
|
||||
|
||||
- finishing before the current interval: skip, go to next
|
||||
- beginning after the current interval: skip and get out of the loop
|
||||
because we are outside range (leaves are ordered)
|
||||
- beginning within the current interval: close the current interval
|
||||
and begin a new current interval that begins at the end of the leave
|
||||
interval
|
||||
- ending within the current interval: update the current interval begin
|
||||
to match the leave interval ending
|
||||
|
||||
:param tuple interval: a tuple (beginning datetime, ending datetime) that
|
||||
is the base interval from which the leave intervals
|
||||
will be removed
|
||||
:param list leave_intervals: a list of tuples (beginning datetime, ending datetime)
|
||||
that are intervals to remove from the base interval
|
||||
:return list intervals: a list of tuples (begin datetime, end datetime)
|
||||
that are the remaining valid intervals """
|
||||
if not interval:
|
||||
return interval
|
||||
if leave_intervals is None:
|
||||
leave_intervals = []
|
||||
:param tuple interval: an interval (see above) that is the base interval
|
||||
from which the leave intervals will be removed
|
||||
:param list leave_intervals: leave intervals to remove
|
||||
:return list intervals: ordered intervals with leaves removed """
|
||||
intervals = []
|
||||
leave_intervals = self._interval_clean(leave_intervals)
|
||||
current_interval = [interval[0], interval[1]]
|
||||
leave_intervals = self._interval_merge(leave_intervals)
|
||||
current_interval = interval
|
||||
for leave in leave_intervals:
|
||||
# skip if ending before the current start datetime
|
||||
if leave[1] <= current_interval[0]:
|
||||
continue
|
||||
# skip if starting after current end datetime; break as leaves are ordered and
|
||||
# are therefore all out of range
|
||||
if leave[0] >= current_interval[1]:
|
||||
break
|
||||
# begins within current interval: close current interval and begin a new one
|
||||
# that begins at the leave end datetime
|
||||
if current_interval[0] < leave[0] < current_interval[1]:
|
||||
current_interval[1] = leave[0]
|
||||
intervals.append((current_interval[0], current_interval[1]))
|
||||
current_interval = [leave[1], interval[1]]
|
||||
intervals.append(self._interval_exclude_right(current_interval, leave))
|
||||
current_interval = self._interval_exclude_left(interval, leave)
|
||||
# ends within current interval: set current start datetime as leave end datetime
|
||||
if current_interval[0] <= leave[1]:
|
||||
current_interval[0] = leave[1]
|
||||
current_interval = self._interval_exclude_left(interval, leave)
|
||||
if current_interval and current_interval[0] < interval[1]: # remove intervals moved outside base interval due to leaves
|
||||
intervals.append((current_interval[0], current_interval[1]))
|
||||
intervals.append(current_interval)
|
||||
return intervals
|
||||
|
||||
@api.model
|
||||
@@ -230,7 +255,7 @@ class ResourceCalendar(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _get_previous_work_day(self, day_date):
|
||||
""" Get previous date of day_date, based on work.calendar. If no
|
||||
""" Get previous date of day_date, based on resource.calendar. If no
|
||||
calendar is provided, just return the previous day.
|
||||
|
||||
:param date day_date: current day as a date
|
||||
@@ -282,7 +307,7 @@ class ResourceCalendar(models.Model):
|
||||
date_to = fields.Datetime.from_string(leave.date_to)
|
||||
if start_datetime and date_to < start_datetime:
|
||||
continue
|
||||
leaves.append((date_from, date_to))
|
||||
leaves.append(self._interval_new(date_from, date_to, {'leaves': leave}))
|
||||
return leaves
|
||||
|
||||
@api.multi
|
||||
@@ -322,14 +347,13 @@ class ResourceCalendar(models.Model):
|
||||
elif start_dt is None:
|
||||
start_dt = datetime.datetime.now().replace(hour=0, minute=0, second=0)
|
||||
else:
|
||||
work_limits.append((start_dt.replace(hour=0, minute=0, second=0), start_dt))
|
||||
work_limits.append(self._interval_new(start_dt.replace(hour=0, minute=0, second=0), start_dt))
|
||||
if end_dt is None:
|
||||
end_dt = start_dt.replace(hour=23, minute=59, second=59)
|
||||
else:
|
||||
work_limits.append((end_dt, end_dt.replace(hour=23, minute=59, second=59)))
|
||||
work_limits.append(self._interval_new(end_dt, end_dt.replace(hour=23, minute=59, second=59)))
|
||||
assert start_dt.date() == end_dt.date(), '_get_day_work_intervals is restricted to one day'
|
||||
|
||||
intervals = []
|
||||
work_dt = start_dt.replace(hour=0, minute=0, second=0)
|
||||
|
||||
working_intervals = []
|
||||
@@ -339,10 +363,10 @@ class ResourceCalendar(models.Model):
|
||||
dt_t = work_dt.replace(hour=0, minute=0, second=0) + timedelta(seconds=(calendar_working_day.hour_to * 3600))
|
||||
|
||||
# adapt tz
|
||||
working_interval = (
|
||||
working_interval = self._interval_new(
|
||||
dt_f.replace(tzinfo=tz_info).astimezone(pytz.UTC).replace(tzinfo=None),
|
||||
dt_t.replace(tzinfo=tz_info).astimezone(pytz.UTC).replace(tzinfo=None),
|
||||
calendar_working_day.id
|
||||
{'attendances': calendar_working_day}
|
||||
)
|
||||
working_intervals += self._interval_remove_leaves(working_interval, work_limits)
|
||||
|
||||
@@ -351,11 +375,10 @@ class ResourceCalendar(models.Model):
|
||||
leaves = self._get_leave_intervals(resource_id=resource_id)
|
||||
|
||||
# filter according to leaves
|
||||
for interval in working_intervals:
|
||||
work_intervals = self._interval_remove_leaves(interval, leaves)
|
||||
intervals += work_intervals
|
||||
|
||||
return intervals
|
||||
if leaves:
|
||||
return list(itertools.chain.from_iterable(map(lambda i: self._interval_remove_leaves(i, leaves), working_intervals)))
|
||||
else:
|
||||
return working_intervals
|
||||
|
||||
# --------------------------------------------------
|
||||
# Main computation API
|
||||
|
||||
@@ -17,31 +17,31 @@ class TestResourceCommon(TransactionCase):
|
||||
|
||||
# Resource data
|
||||
# Calendar working days: 1 (8-16 -> 8hours), 4 (8-13, 16-23 -> 12hours)
|
||||
self.calendar = self.env['resource.calendar'].create(
|
||||
{
|
||||
'name': 'TestCalendar',
|
||||
'attendance_ids': [
|
||||
(0, 0, {
|
||||
'name': 'Att1',
|
||||
'dayofweek': '1',
|
||||
'hour_from': 8,
|
||||
'hour_to': 16,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'Att2',
|
||||
'dayofweek': '4',
|
||||
'hour_from': 8,
|
||||
'hour_to': 13,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'Att3',
|
||||
'dayofweek': '4',
|
||||
'hour_from': 16,
|
||||
'hour_to': 23,
|
||||
})
|
||||
]
|
||||
}
|
||||
)
|
||||
self.calendar = self.env['resource.calendar'].create({
|
||||
'name': 'TestCalendar',
|
||||
'attendance_ids': [(5, 0, 0)]
|
||||
})
|
||||
self.att_1 = self.env['resource.calendar.attendance'].create({
|
||||
'name': 'Att1',
|
||||
'calendar_id': self.calendar.id,
|
||||
'dayofweek': '1',
|
||||
'hour_from': 8,
|
||||
'hour_to': 16
|
||||
})
|
||||
self.att_2 = self.env['resource.calendar.attendance'].create({
|
||||
'name': 'Att2',
|
||||
'calendar_id': self.calendar.id,
|
||||
'dayofweek': '4',
|
||||
'hour_from': 8,
|
||||
'hour_to': 13
|
||||
})
|
||||
self.att_3 = self.env['resource.calendar.attendance'].create({
|
||||
'name': 'Att3',
|
||||
'calendar_id': self.calendar.id,
|
||||
'dayofweek': '4',
|
||||
'hour_from': 16,
|
||||
'hour_to': 23
|
||||
})
|
||||
|
||||
self.resource1_id = self.env['resource.resource'].create(
|
||||
{
|
||||
@@ -53,31 +53,22 @@ class TestResourceCommon(TransactionCase):
|
||||
).id
|
||||
|
||||
# Leave1: 19/02/2013, from 9 to 12, is a day 1
|
||||
self.leave1_id = self.env['resource.calendar.leaves'].create(
|
||||
{
|
||||
'name': 'GenericLeave',
|
||||
'calendar_id': self.calendar.id,
|
||||
'date_from': Datetime.from_string('2013-02-19 09:00:00'),
|
||||
'date_to': Datetime.from_string('2013-02-19 12:00:00'),
|
||||
}
|
||||
).id
|
||||
self.leave1 = self.env['resource.calendar.leaves'].create({
|
||||
'name': 'GenericLeave',
|
||||
'calendar_id': self.calendar.id,
|
||||
'date_from': Datetime.from_string('2013-02-19 09:00:00'),
|
||||
'date_to': Datetime.from_string('2013-02-19 12:00:00')})
|
||||
# Leave2: 22/02/2013, from 9 to 15, is a day 4
|
||||
self.leave2_id = self.env['resource.calendar.leaves'].create(
|
||||
{
|
||||
'name': 'ResourceLeave',
|
||||
'calendar_id': self.calendar.id,
|
||||
'resource_id': self.resource1_id,
|
||||
'date_from': Datetime.from_string('2013-02-22 09:00:00'),
|
||||
'date_to': Datetime.from_string('2013-02-22 15:00:00'),
|
||||
}
|
||||
).id
|
||||
self.leave2 = self.env['resource.calendar.leaves'].create({
|
||||
'name': 'ResourceLeave',
|
||||
'calendar_id': self.calendar.id,
|
||||
'resource_id': self.resource1_id,
|
||||
'date_from': Datetime.from_string('2013-02-22 09:00:00'),
|
||||
'date_to': Datetime.from_string('2013-02-22 15:00:00')})
|
||||
# Leave3: 25/02/2013 (day0) -> 01/03/2013 (day4)
|
||||
self.leave3_id = self.env['resource.calendar.leaves'].create(
|
||||
{
|
||||
'name': 'ResourceLeave2',
|
||||
'calendar_id': self.calendar.id,
|
||||
'resource_id': self.resource1_id,
|
||||
'date_from': Datetime.from_string('2013-02-25 13:00:00'),
|
||||
'date_to': Datetime.from_string('2013-03-01 11:30:00'),
|
||||
}
|
||||
).id
|
||||
self.leave3 = self.env['resource.calendar.leaves'].create({
|
||||
'name': 'ResourceLeave2',
|
||||
'calendar_id': self.calendar.id,
|
||||
'resource_id': self.resource1_id,
|
||||
'date_from': Datetime.from_string('2013-02-25 13:00:00'),
|
||||
'date_to': Datetime.from_string('2013-03-01 11:30:00')})
|
||||
|
||||
@@ -14,7 +14,7 @@ from odoo.addons.resource.tests.common import TestResourceCommon
|
||||
class TestIntervals(TestResourceCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestResourceCommon, self).setUp()
|
||||
super(TestIntervals, self).setUp()
|
||||
# Some data intervals
|
||||
# - first one is included in second one
|
||||
# - second one is extended by third one
|
||||
@@ -22,62 +22,62 @@ class TestIntervals(TestResourceCommon):
|
||||
# - fifth one is prior to other one
|
||||
# Once cleaned: 1 interval 03/02 8-10), 2 intervals 04/02 (8-14 + 17-21)
|
||||
self.intervals = [
|
||||
(
|
||||
self.calendar._interval_new(
|
||||
Datetime.from_string('2013-02-04 09:00:00'),
|
||||
Datetime.from_string('2013-02-04 11:00:00')
|
||||
), (
|
||||
), self.calendar._interval_new(
|
||||
Datetime.from_string('2013-02-04 08:00:00'),
|
||||
Datetime.from_string('2013-02-04 12:00:00')
|
||||
), (
|
||||
), self.calendar._interval_new(
|
||||
Datetime.from_string('2013-02-04 11:00:00'),
|
||||
Datetime.from_string('2013-02-04 14:00:00')
|
||||
), (
|
||||
), self.calendar._interval_new(
|
||||
Datetime.from_string('2013-02-04 17:00:00'),
|
||||
Datetime.from_string('2013-02-04 21:00:00')
|
||||
), (
|
||||
), self.calendar._interval_new(
|
||||
Datetime.from_string('2013-02-03 08:00:00'),
|
||||
Datetime.from_string('2013-02-03 10:00:00')
|
||||
), (
|
||||
), self.calendar._interval_new(
|
||||
Datetime.from_string('2013-02-04 18:00:00'),
|
||||
Datetime.from_string('2013-02-04 19:00:00')
|
||||
)
|
||||
]
|
||||
|
||||
def test_interval_clean(self):
|
||||
cleaned_intervals = self.env['resource.calendar']._interval_clean(self.intervals)
|
||||
def test_interval_merge(self):
|
||||
cleaned_intervals = self.env['resource.calendar']._interval_merge(self.intervals)
|
||||
self.assertEqual(len(cleaned_intervals), 3)
|
||||
# First interval: 03, unchanged
|
||||
self.assertEqual(cleaned_intervals[0], (Datetime.from_string('2013-02-03 08:00:00'), Datetime.from_string('2013-02-03 10:00:00')))
|
||||
self.assertEqual(cleaned_intervals[0][:2], (Datetime.from_string('2013-02-03 08:00:00'), Datetime.from_string('2013-02-03 10:00:00')))
|
||||
# Second interval: 04, 08-14, combining 08-12 and 11-14, 09-11 being inside 08-12
|
||||
self.assertEqual(cleaned_intervals[1], (Datetime.from_string('2013-02-04 08:00:00'), Datetime.from_string('2013-02-04 14:00:00')))
|
||||
self.assertEqual(cleaned_intervals[1][:2], (Datetime.from_string('2013-02-04 08:00:00'), Datetime.from_string('2013-02-04 14:00:00')))
|
||||
# Third interval: 04, 17-21, 18-19 being inside 17-21
|
||||
self.assertEqual(cleaned_intervals[2], (Datetime.from_string('2013-02-04 17:00:00'), Datetime.from_string('2013-02-04 21:00:00')))
|
||||
self.assertEqual(cleaned_intervals[2][:2], (Datetime.from_string('2013-02-04 17:00:00'), Datetime.from_string('2013-02-04 21:00:00')))
|
||||
|
||||
def test_interval_remove(self):
|
||||
working_interval = (Datetime.from_string('2013-02-04 08:00:00'), Datetime.from_string('2013-02-04 18:00:00'))
|
||||
working_interval = self.calendar._interval_new(Datetime.from_string('2013-02-04 08:00:00'), Datetime.from_string('2013-02-04 18:00:00'))
|
||||
result = self.env['resource.calendar']._interval_remove_leaves(working_interval, self.intervals)
|
||||
self.assertEqual(len(result), 1)
|
||||
# First interval: 04, 14-17
|
||||
self.assertEqual(result[0], (Datetime.from_string('2013-02-04 14:00:00'), Datetime.from_string('2013-02-04 17:00:00')))
|
||||
self.assertEqual(result[0][:2], (Datetime.from_string('2013-02-04 14:00:00'), Datetime.from_string('2013-02-04 17:00:00')))
|
||||
|
||||
def test_interval_schedule_hours(self):
|
||||
cleaned_intervals = self.env['resource.calendar']._interval_clean(self.intervals)
|
||||
cleaned_intervals = self.env['resource.calendar']._interval_merge(self.intervals)
|
||||
result = self.env['resource.calendar']._interval_schedule_hours(cleaned_intervals, 5.5)
|
||||
self.assertEqual(len(result), 2)
|
||||
# First interval: 03, 8-10 untouched
|
||||
self.assertEqual(result[0], (Datetime.from_string('2013-02-03 08:00:00'), Datetime.from_string('2013-02-03 10:00:00')))
|
||||
self.assertEqual(result[0][:2], (Datetime.from_string('2013-02-03 08:00:00'), Datetime.from_string('2013-02-03 10:00:00')))
|
||||
# First interval: 04, 08-11:30
|
||||
self.assertEqual(result[1], (Datetime.from_string('2013-02-04 08:00:00'), Datetime.from_string('2013-02-04 11:30:00')))
|
||||
self.assertEqual(result[1][:2], (Datetime.from_string('2013-02-04 08:00:00'), Datetime.from_string('2013-02-04 11:30:00')))
|
||||
|
||||
def test_interval_schedule_hours_backwards(self):
|
||||
cleaned_intervals = self.env['resource.calendar']._interval_clean(self.intervals)
|
||||
cleaned_intervals = self.env['resource.calendar']._interval_merge(self.intervals)
|
||||
cleaned_intervals.reverse()
|
||||
result = self.env['resource.calendar']._interval_schedule_hours(cleaned_intervals, 5.5, remove_at_end=False)
|
||||
self.assertEqual(len(result), 2)
|
||||
# First interval: 03, 8-10 untouched
|
||||
self.assertEqual(result[0], (Datetime.from_string('2013-02-04 17:00:00'), Datetime.from_string('2013-02-04 21:00:00')))
|
||||
self.assertEqual(result[0][:2], (Datetime.from_string('2013-02-04 17:00:00'), Datetime.from_string('2013-02-04 21:00:00')))
|
||||
# First interval: 04, 08-11:30
|
||||
self.assertEqual(result[1], (Datetime.from_string('2013-02-04 12:30:00'), Datetime.from_string('2013-02-04 14:00:00')))
|
||||
self.assertEqual(result[1][:2], (Datetime.from_string('2013-02-04 12:30:00'), Datetime.from_string('2013-02-04 14:00:00')))
|
||||
|
||||
|
||||
class TestCalendarBasics(TestResourceCommon):
|
||||
@@ -124,32 +124,40 @@ class TestCalendarBasics(TestResourceCommon):
|
||||
# Test: day0 without leaves: 1 interval
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=Datetime.from_string('2013-02-12 09:08:07'))
|
||||
self.assertEqual(len(intervals), 1)
|
||||
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-12 09:08:07'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-12 09:08:07'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(intervals[0][2]['attendances'], self.att_1)
|
||||
|
||||
# Test: day1, beginning at 10:30 -> work from 10:30 (arrival) until 16:00
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=Datetime.from_string('2013-02-19 10:30:00'))
|
||||
self.assertEqual(len(intervals), 1)
|
||||
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-19 10:30:00'), Datetime.from_string('2013-02-19 16:00:00')))
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-19 10:30:00'), Datetime.from_string('2013-02-19 16:00:00')))
|
||||
self.assertEqual(intervals[0][2]['attendances'], self.att_1)
|
||||
|
||||
# Test: day3 without leaves: 2 interval
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=Datetime.from_string('2013-02-15 10:11:12'))
|
||||
self.assertEqual(len(intervals), 2)
|
||||
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(intervals[1], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(intervals[1][:2], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(intervals[0][2]['attendances'], self.att_2)
|
||||
self.assertEqual(intervals[1][2]['attendances'], self.att_3)
|
||||
|
||||
def test_calendar_working_day_intervals_leaves_generic(self):
|
||||
# Test: day0 with leaves outside range: 1 interval
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=Datetime.from_string('2013-02-12 07:00:00'), compute_leaves=True)
|
||||
self.assertEqual(len(intervals), 1)
|
||||
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-12 08:00:00'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-12 08:00:00'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
|
||||
# Test: day0 with leaves: 2 intervals because of leave between 9 ans 12, ending at 15:45:30
|
||||
# Test: day0 with leaves: 2 intervals because of leave between 9 and 12, ending at 15:45:30
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=Datetime.from_string('2013-02-19 08:15:00'),
|
||||
end_dt=Datetime.from_string('2013-02-19 15:45:30'),
|
||||
compute_leaves=True)
|
||||
self.assertEqual(len(intervals), 2)
|
||||
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-19 08:15:00'), Datetime.from_string('2013-02-19 09:00:00')))
|
||||
self.assertEqual(intervals[1], (Datetime.from_string('2013-02-19 12:00:00'), Datetime.from_string('2013-02-19 15:45:30')))
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-19 08:15:00'), Datetime.from_string('2013-02-19 09:00:00')))
|
||||
self.assertEqual(intervals[1][:2], (Datetime.from_string('2013-02-19 12:00:00'), Datetime.from_string('2013-02-19 15:45:30')))
|
||||
self.assertEqual(intervals[0][2]['attendances'], self.att_1)
|
||||
self.assertEqual(intervals[0][2]['leaves'], self.leave1)
|
||||
self.assertEqual(intervals[1][2]['attendances'], self.att_1)
|
||||
self.assertEqual(intervals[0][2]['leaves'], self.leave1)
|
||||
|
||||
def test_calendar_working_day_intervals_leaves_resource(self):
|
||||
# Test: day1+14 on leave, with resource leave computation
|
||||
@@ -159,7 +167,7 @@ class TestCalendarBasics(TestResourceCommon):
|
||||
resource_id=self.resource1_id
|
||||
)
|
||||
# Result: nothing, because on leave
|
||||
self.assertEqual(len(intervals), 0, 'resource_calendar: wrong working interval/day computing')
|
||||
self.assertEqual(len(intervals), 0)
|
||||
|
||||
def test_calendar_working_day_intervals_limited_attendances(self):
|
||||
""" Test attendances limited in time. """
|
||||
@@ -170,21 +178,24 @@ class TestCalendarBasics(TestResourceCommon):
|
||||
'date_to': False,
|
||||
})
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=self.date2)
|
||||
self.assertEqual(intervals, [(Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00'))])
|
||||
self.assertEqual(len(intervals), 1)
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
|
||||
attendance.write({
|
||||
'date_from': False,
|
||||
'date_to': self.date2 - relativedelta(days=7),
|
||||
})
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=self.date2)
|
||||
self.assertEqual(intervals, [(Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00'))])
|
||||
self.assertEqual(len(intervals), 1)
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
|
||||
attendance.write({
|
||||
'date_from': self.date2 + relativedelta(days=7),
|
||||
'date_to': self.date2 - relativedelta(days=7),
|
||||
})
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=self.date2)
|
||||
self.assertEqual(intervals, [(Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00'))])
|
||||
self.assertEqual(len(intervals), 1)
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
|
||||
attendance.write({
|
||||
'date_from': self.date2,
|
||||
@@ -192,13 +203,13 @@ class TestCalendarBasics(TestResourceCommon):
|
||||
})
|
||||
intervals = self.calendar._get_day_work_intervals(start_dt=self.date2)
|
||||
self.assertEqual(len(intervals), 2)
|
||||
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(intervals[1], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(intervals[0][:2], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(intervals[1][:2], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
|
||||
def test_calendar_working_hours_of_date(self):
|
||||
# Test: day1, beginning at 10:30 -> work from 10:30 (arrival) until 16:00
|
||||
wh = self.calendar.get_work_hours_count(Datetime.from_string('2013-02-19 10:30:00'), Datetime.from_string('2013-02-19 18:00:00'))
|
||||
self.assertEqual(wh, 5.5, 'resource_calendar: wrong working interval / day time computing')
|
||||
self.assertEqual(wh, 5.5)
|
||||
|
||||
|
||||
class ResourceWorkingHours(TestResourceCommon):
|
||||
@@ -224,15 +235,15 @@ class ResourceWorkingHours(TestResourceCommon):
|
||||
def test_calendar_hours_scheduling_backward(self):
|
||||
res = self.calendar._schedule_hours(-40, day_dt=Datetime.from_string('2013-02-12 09:00:00'))
|
||||
# current day, limited at 09:00 because of day_dt specified -> 1 hour
|
||||
self.assertEqual(res[-1], (Datetime.from_string('2013-02-12 08:00:00'), Datetime.from_string('2013-02-12 09:00:00')))
|
||||
self.assertEqual(res[-1][:2], (Datetime.from_string('2013-02-12 08:00:00'), Datetime.from_string('2013-02-12 09:00:00')))
|
||||
# previous days: 5+7 hours / 8 hours / 5+7 hours -> 32 hours
|
||||
self.assertEqual(res[-2], (Datetime.from_string('2013-02-08 16:00:00'), Datetime.from_string('2013-02-08 23:00:00')))
|
||||
self.assertEqual(res[-3], (Datetime.from_string('2013-02-08 08:00:00'), Datetime.from_string('2013-02-08 13:00:00')))
|
||||
self.assertEqual(res[-4], (Datetime.from_string('2013-02-05 08:00:00'), Datetime.from_string('2013-02-05 16:00:00')))
|
||||
self.assertEqual(res[-5], (Datetime.from_string('2013-02-01 16:00:00'), Datetime.from_string('2013-02-01 23:00:00')))
|
||||
self.assertEqual(res[-6], (Datetime.from_string('2013-02-01 08:00:00'), Datetime.from_string('2013-02-01 13:00:00')))
|
||||
self.assertEqual(res[-2][:2], (Datetime.from_string('2013-02-08 16:00:00'), Datetime.from_string('2013-02-08 23:00:00')))
|
||||
self.assertEqual(res[-3][:2], (Datetime.from_string('2013-02-08 08:00:00'), Datetime.from_string('2013-02-08 13:00:00')))
|
||||
self.assertEqual(res[-4][:2], (Datetime.from_string('2013-02-05 08:00:00'), Datetime.from_string('2013-02-05 16:00:00')))
|
||||
self.assertEqual(res[-5][:2], (Datetime.from_string('2013-02-01 16:00:00'), Datetime.from_string('2013-02-01 23:00:00')))
|
||||
self.assertEqual(res[-6][:2], (Datetime.from_string('2013-02-01 08:00:00'), Datetime.from_string('2013-02-01 13:00:00')))
|
||||
# 7 hours remaining
|
||||
self.assertEqual(res[-7], (Datetime.from_string('2013-01-29 09:00:00'), Datetime.from_string('2013-01-29 16:00:00')))
|
||||
self.assertEqual(res[-7][:2], (Datetime.from_string('2013-01-29 09:00:00'), Datetime.from_string('2013-01-29 16:00:00')))
|
||||
|
||||
# Compute scheduled hours
|
||||
td = timedelta()
|
||||
@@ -242,13 +253,13 @@ class ResourceWorkingHours(TestResourceCommon):
|
||||
|
||||
def test_calendar_hours_scheduling_forward(self):
|
||||
res = self.calendar._schedule_hours(40, day_dt=Datetime.from_string('2013-02-12 09:00:00'))
|
||||
self.assertEqual(res[0], (Datetime.from_string('2013-02-12 09:00:00'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(res[1], (Datetime.from_string('2013-02-15 08:00:00'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(res[2], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(res[3], (Datetime.from_string('2013-02-19 08:00:00'), Datetime.from_string('2013-02-19 16:00:00')))
|
||||
self.assertEqual(res[4], (Datetime.from_string('2013-02-22 08:00:00'), Datetime.from_string('2013-02-22 13:00:00')))
|
||||
self.assertEqual(res[5], (Datetime.from_string('2013-02-22 16:00:00'), Datetime.from_string('2013-02-22 23:00:00')))
|
||||
self.assertEqual(res[6], (Datetime.from_string('2013-02-26 08:00:00'), Datetime.from_string('2013-02-26 09:00:00')))
|
||||
self.assertEqual(res[0][:2], (Datetime.from_string('2013-02-12 09:00:00'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(res[1][:2], (Datetime.from_string('2013-02-15 08:00:00'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(res[2][:2], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(res[3][:2], (Datetime.from_string('2013-02-19 08:00:00'), Datetime.from_string('2013-02-19 16:00:00')))
|
||||
self.assertEqual(res[4][:2], (Datetime.from_string('2013-02-22 08:00:00'), Datetime.from_string('2013-02-22 13:00:00')))
|
||||
self.assertEqual(res[5][:2], (Datetime.from_string('2013-02-22 16:00:00'), Datetime.from_string('2013-02-22 23:00:00')))
|
||||
self.assertEqual(res[6][:2], (Datetime.from_string('2013-02-26 08:00:00'), Datetime.from_string('2013-02-26 09:00:00')))
|
||||
|
||||
td = timedelta()
|
||||
for item in res:
|
||||
@@ -260,15 +271,15 @@ class ResourceWorkingHours(TestResourceCommon):
|
||||
40, day_dt=Datetime.from_string('2013-02-12 09:00:00'),
|
||||
compute_leaves=True, resource_id=self.resource1_id
|
||||
)
|
||||
self.assertEqual(res[0], (Datetime.from_string('2013-02-12 09:00:00'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(res[1], (Datetime.from_string('2013-02-15 08:00:00'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(res[2], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(res[3], (Datetime.from_string('2013-02-19 08:00:00'), Datetime.from_string('2013-02-19 09:00:00')))
|
||||
self.assertEqual(res[4], (Datetime.from_string('2013-02-19 12:00:00'), Datetime.from_string('2013-02-19 16:00:00')))
|
||||
self.assertEqual(res[5], (Datetime.from_string('2013-02-22 08:00:00'), Datetime.from_string('2013-02-22 09:00:00')))
|
||||
self.assertEqual(res[6], (Datetime.from_string('2013-02-22 16:00:00'), Datetime.from_string('2013-02-22 23:00:00')))
|
||||
self.assertEqual(res[7], (Datetime.from_string('2013-03-01 11:30:00'), Datetime.from_string('2013-03-01 13:00:00')))
|
||||
self.assertEqual(res[8], (Datetime.from_string('2013-03-01 16:00:00'), Datetime.from_string('2013-03-01 22:30:00')))
|
||||
self.assertEqual(res[0][:2], (Datetime.from_string('2013-02-12 09:00:00'), Datetime.from_string('2013-02-12 16:00:00')))
|
||||
self.assertEqual(res[1][:2], (Datetime.from_string('2013-02-15 08:00:00'), Datetime.from_string('2013-02-15 13:00:00')))
|
||||
self.assertEqual(res[2][:2], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
|
||||
self.assertEqual(res[3][:2], (Datetime.from_string('2013-02-19 08:00:00'), Datetime.from_string('2013-02-19 09:00:00')))
|
||||
self.assertEqual(res[4][:2], (Datetime.from_string('2013-02-19 12:00:00'), Datetime.from_string('2013-02-19 16:00:00')))
|
||||
self.assertEqual(res[5][:2], (Datetime.from_string('2013-02-22 08:00:00'), Datetime.from_string('2013-02-22 09:00:00')))
|
||||
self.assertEqual(res[6][:2], (Datetime.from_string('2013-02-22 16:00:00'), Datetime.from_string('2013-02-22 23:00:00')))
|
||||
self.assertEqual(res[7][:2], (Datetime.from_string('2013-03-01 11:30:00'), Datetime.from_string('2013-03-01 13:00:00')))
|
||||
self.assertEqual(res[8][:2], (Datetime.from_string('2013-03-01 16:00:00'), Datetime.from_string('2013-03-01 22:30:00')))
|
||||
|
||||
td = timedelta()
|
||||
for item in res:
|
||||
|
||||
@@ -92,7 +92,7 @@ class ResourceCalendar(models.Model):
|
||||
if leave_intervals is None:
|
||||
leave_intervals = []
|
||||
intervals = []
|
||||
#leave_intervals = self._interval_clean(leave_intervals) NOT NECESSARY TO CLEAN HERE AS IT WOULD REMOVE GROUP INFO
|
||||
#leave_intervals = self._interval_merge(leave_intervals) NOT NECESSARY TO CLEAN HERE AS IT WOULD REMOVE GROUP INFO
|
||||
current_interval = list(interval)
|
||||
for leave in leave_intervals:
|
||||
if len(leave) > 2:
|
||||
|
||||
Reference in New Issue
Block a user