diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index b594df319cc..941fce80c96 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -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 diff --git a/addons/resource/tests/common.py b/addons/resource/tests/common.py index 9ff66ca9394..0fd6f9f7d9b 100644 --- a/addons/resource/tests/common.py +++ b/addons/resource/tests/common.py @@ -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')}) diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index 3190c6c8345..def52120c28 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -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: diff --git a/addons/stock_calendar/models/resource.py b/addons/stock_calendar/models/resource.py index 519a8e3d5e0..98cc218b77b 100644 --- a/addons/stock_calendar/models/resource.py +++ b/addons/stock_calendar/models/resource.py @@ -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: