[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:
Thibault Delavallée
2017-02-27 14:04:48 +01:00
parent be01aaa99f
commit ac26fa3dd4
4 changed files with 192 additions and 167 deletions
+80 -57
View File
@@ -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
+42 -51
View File
@@ -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')})
+69 -58
View File
@@ -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:
+1 -1
View File
@@ -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: