[IMP] resource: first refactoring of various methods to calculate timed intervals based on resource.calendar. Added tests.

bzr revid: tde@openerp.com-20130821105109-1rtynzx69hmslxey
This commit is contained in:
Thibault Delavallée 2013-08-21 12:51:09 +02:00
parent 00d3d9a4b6
commit a67459018c
5 changed files with 668 additions and 16 deletions

View File

@ -20,17 +20,19 @@
##############################################################################
import pytz
from datetime import datetime, timedelta
from datetime import date, datetime, timedelta
from dateutil import rrule
from dateutil.relativedelta import relativedelta
import itertools
import math
from operator import itemgetter
from faces import *
from openerp import tools
from openerp.osv import fields, osv
from openerp.tools.float_utils import float_compare
from openerp.tools.translate import _
from itertools import groupby
from operator import itemgetter
class resource_calendar(osv.osv):
_name = "resource.calendar"
@ -40,25 +42,321 @@ class resource_calendar(osv.osv):
'company_id' : fields.many2one('res.company', 'Company', required=False),
'attendance_ids' : fields.one2many('resource.calendar.attendance', 'calendar_id', 'Working Time'),
'manager' : fields.many2one('res.users', 'Workgroup Manager'),
'leave_ids': fields.one2many(
'resource.calendar.leaves', 'calendar_id', 'Leaves',
help=''
),
}
_defaults = {
'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'resource.calendar', context=context)
}
def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None):
"""Calculates the Working Total Hours based on Resource Calendar and
given working day (datetime object).
# --------------------------------------------------
# Utility methods
# --------------------------------------------------
@param resource_calendar_id: resource.calendar browse record
@param day: datetime object
def interval_clean(self, intervals):
""" Utility method that removes overlapping inside datetime intervals.
@return: returns the working hours (as float) men should work on the given day if is in the attendance_ids of the resource_calendar_id (i.e if that day is a working day), returns 0.0 otherwise
:param list intervals: list of datetime intervals. Each interval is a
tuple(datetime_from, datetime_to)
:return list final_list: list of intervals without overlap
"""
res = 0.0
for working_day in resource_calendar_id.attendance_ids:
if (int(working_day.dayofweek) + 1) == day.isoweekday():
res += working_day.hour_to - working_day.hour_from
return res
intervals = sorted(intervals) # TODO: check sorted method
final_list = []
working_interval = None
while intervals:
current_interval = intervals.pop(0)
if not working_interval: # init
working_interval = [current_interval[0], current_interval[1]]
elif working_interval[1] < current_interval[0]: # interval is disjoint
final_list.append(tuple(working_interval))
working_interval = [current_interval[0], current_interval[1]]
elif working_interval[1] < current_interval[1]: # union of greater intervals
working_interval[1] = current_interval[1]
if working_interval: # handle void lists
final_list.append(tuple(working_interval))
return final_list
def interval_remove_leaves(self, interval, leave_intervals):
""" Utility method that remove leave intervals from a base interval:
- clean the leave intrevals, 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 interval: a list of tuples (beginning datetime, ending datetime)
that are intervals to remove from the base interval
:return list final_list: 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 = []
final_list = []
leave_intervals = self.interval_clean(leave_intervals)
current_interval = [interval[0], interval[1]]
# print '\tcurrent_intreval', current_interval
for leave in leave_intervals:
# print '\thandling leave', leave
if leave[1] <= current_interval[0]:
# print '\t\tbefore, skipping'
continue
if leave[0] >= current_interval[1]:
# print '\t\tafter, abort'
break
if current_interval[0] < leave[0] < current_interval[1]:
# print '\t\tbeginning inside'
current_interval[1] = leave[0]
final_list.append((current_interval[0], current_interval[1]))
current_interval = [leave[1], interval[1]]
if current_interval[0] <= leave[1] <= current_interval[1]:
# print '\t\tending inside'
current_interval[0] = leave[1]
if current_interval and current_interval[0] < interval[1]: # remove intervals moved outside base interval due to leaves
final_list.append((current_interval[0], current_interval[1]))
return final_list
# --------------------------------------------------
# Date and hours computation
# --------------------------------------------------
def get_next_day(self, cr, uid, id, day_date, context=None):
if id is None:
return day_date + relativedelta(days=1)
calendar = self.browse(cr, uid, id, context=None)
weekdays = set()
for attendance in calendar.attendance_ids:
weekdays.add(int(attendance.dayofweek))
weekdays = list(weekdays)
if day_date.weekday() in weekdays:
base_index = weekdays.index(day_date.weekday())
else:
base_index = -1
for weekday in weekdays:
if weekday > day_date.weekday():
break
base_index += 1
new_index = (base_index + 1) % len(weekdays)
days = (weekdays[new_index] - day_date.weekday())
if days < 0:
days = 7 + days
return day_date + relativedelta(days=days)
def get_previous_day(self, cr, uid, id, day_date, context=None):
if id is None:
return day_date + relativedelta(days=-1)
calendar = self.browse(cr, uid, id, context=None)
weekdays = set()
for attendance in calendar.attendance_ids:
weekdays.add(int(attendance.dayofweek))
weekdays = list(weekdays)
weekdays.reverse()
if day_date.weekday() in weekdays:
base_index = weekdays.index(day_date.weekday())
else:
base_index = -1
for weekday in weekdays:
if weekday < day_date.weekday():
break
base_index += 1
new_index = (base_index + 1) % len(weekdays)
days = (weekdays[new_index] - day_date.weekday())
if days > 0:
days = days - 7
return day_date + relativedelta(days=days)
def _get_leave_intervals(self, cr, uid, id, resource_id=None, start_datetime=None, end_datetime=None, context=None):
"""Get the leaves of the calendar. Leaves can be filtered on the resource,
the start datetime or the end datetime.
:param int resource_id: if set, global + specific leaves will be taken
into account
TODO: COMPLETE ME
"""
resource_calendar = self.browse(cr, uid, id, context=context)
leaves = []
for leave in resource_calendar.leave_ids:
if resource_id and leave.resource_id and not resource_id == leave.resource_id.id:
continue
date_from = datetime.strptime(leave.date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT)
if start_datetime and date_from < start_datetime:
continue
if end_datetime and date_end > end_datetime:
continue
date_to = datetime.strptime(leave.date_to, tools.DEFAULT_SERVER_DATETIME_FORMAT)
leaves.append((date_from, date_to))
return leaves
def get_working_intervals_of_day(self, cr, uid, id, day_date=None, leaves=None, compute_leaves=False, resource_id=None, context=None):
"""Get the working intervals of the day based on calendar. This method
handle leaves that come directly from the leaves parameter or can be computed.
:param int id: resource.calendar id; take the first one if is a list
:param date day_date: date object that is the day for which this method
computes the working intervals; is None, set to today
:param list leaves: a list of tuples(start_datetime, end_datetime) that
represent leaves.
:param boolean compute_leaves: if set and if leaves is None, compute the
leaves based on calendar and resource.
If leaves is None and compute_leaves false
no leaves are taken into account.
:param int resource_id: the id of the resource to take into account when
computing the leaves. If not set, only general
leaves will be computed.
:returns list intervals: a list of tuples (start_datetime, end_datetime)
that are intervals of work
"""
if id is None:
return []
if isinstance(id, (list, tuple)):
id = id[0]
if day_date is None:
day_date = date.today()
resource_calendar = self.browse(cr, uid, id, context=context)
intervals = []
# find working intervals
date_dict = {
'Y': day_date.year,
'm': day_date.month,
'd': day_date.day,
}
working_intervals = []
for calendar_working_day in resource_calendar.attendance_ids:
if int(calendar_working_day.dayofweek) == day_date.weekday():
date_dict.update({
'HF': calendar_working_day.hour_from,
'HT': calendar_working_day.hour_to,
})
date_from = datetime.strptime('%(Y)04d-%(m)02d-%(d)02d %(HF)02d:00:00' % date_dict, '%Y-%m-%d %H:%M:%S')
date_to = datetime.strptime('%(Y)04d-%(m)02d-%(d)02d %(HT)02d:00:00' % date_dict, '%Y-%m-%d %H:%M:%S')
working_intervals.append((date_from, date_to))
# find leave intervals
if leaves is None and compute_leaves:
leaves = self._get_leave_intervals(cr, uid, id, resource_id=resource_id, context=None)
# filter according to leaves
for interval in working_intervals:
work_intervals = self.interval_remove_leaves(interval, leaves)
intervals += work_intervals
return intervals
def get_working_hours_of_date(self, cr, uid, id, day_date=None, leaves=None, compute_leaves=False, resource_id=None, context=None):
"""Get the working hours of the day based on calendar. This method uses
get_working_intervals_of_day to have the work intervals of the day. It
then calculates the number of hours contained in those intervals. """
res = timedelta()
intervals = self.get_working_intervals_of_day(cr, uid, id, day_date, leaves, compute_leaves, resource_id, context)
for interval in intervals:
res += interval[1] - interval[0]
return (res.total_seconds() / 3600.0)
def schedule_hours(self, cr, uid, id, hours, start_datetime=None, end_datetime=None, compute_leaves=False, resource_id=None, context=None):
"""
"""
if start_datetime is None:
start_datetime = datetime.now()
work_hours = 0
iterations = 0
final_intervals = []
# compute work days
work_days = set()
resource_calendar = self.browse(cr, uid, id, context=context)
for attendance in resource_calendar.attendance_ids:
work_days.add(int(attendance.dayofweek))
# prepare rrule arguments
rrule_args = {
'byweekday': work_days,
'dtstart': start_datetime,
}
if end_date:
rrule_args['until'] = end_datetime
else:
rrule_args['count'] = 1024
for day in rrule.rrule(rrule.DAILY, **rrule_args):
working_intervals = self.get_working_intervals_of_day(cr, uid, id, day_date=day, compute_leaves=compute_leaves, resource_id=resource_id, context=context)
if not working_intervals:
continue
# Compute worked hours, compare to requested number of hours
res = timedelta()
for interval in working_intervals:
res += interval[1] - interval[0]
work_hours += (res.total_seconds() / 3600.0)
final_intervals += working_intervals
if float_compare(work_hours, hours * 1.0, precision_digits=2) in (0, 1) or (iterations >= 50):
break
iterations += 1
return final_intervals
def _schedule_days(self, cr, uid, id, days, date=None, compute_leaves=False, resource_id=None, context=None):
"""Schedule days of work.
This method can be used backwards, i.e. scheduling days before a deadline.
"""
backwards = False
if days < 0:
backwards = True
days = abs(days)
intervals = []
planned_days = 0
iterations = 0
current_datetime = date
while planned_days < days and iterations < 1000:
working_intervals = self.get_working_intervals_of_day(cr, uid, id, current_datetime, compute_leaves=compute_leaves, resource_id=resource_id, context=context)
if id is None or working_intervals: # no calendar -> no working hours, but day is considered as worked
planned_days += 1
intervals += working_intervals
# get next day
if backwards:
current_datetime = self.get_previous_day(cr, uid, id, current_datetime)
else:
current_datetime = self.get_next_day(cr, uid, id, current_datetime)
return (current_datetime, intervals)
def schedule_days(self, cr, uid, id, days, date=None, compute_leaves=False, resource_id=None, context=None):
res = self._schedule_days(cr, uid, id, days, date, compute_leaves, resource_id, context)
return res[0]
# --------------------------------------------------
# Compaqtibility / to clean / to remove
# --------------------------------------------------
def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None):
""" Compatibility method - will be removed for OpenERP v8
TDE TODO: hr_payroll/hr_payroll.py
"""
return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, day_date=day, context=None)
def _get_leaves(self, cr, uid, id, resource):
"""Private Method to Calculate resource Leaves days
@ -99,6 +397,8 @@ class resource_calendar(osv.osv):
schedule.
@return : List datetime object of working schedule based on supplies
params
TDE TODO: used in mrp_operations/mrp_operations.py
"""
if not id:
td = int(hours)*3
@ -138,9 +438,10 @@ class resource_calendar(osv.osv):
# def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True):
def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True):
""" TDE NOTE: used in mrp_operations/mrp_operations.py and in interval_get() """
def group(lst, key):
lst.sort(key=itemgetter(key))
grouped = groupby(lst, itemgetter(key))
grouped = itertools.groupby(lst, itemgetter(key))
return dict([(k, [v for v in itr]) for k, itr in grouped])
# END group
@ -198,6 +499,8 @@ class resource_calendar(osv.osv):
@param byday: boolean flag bit enforce day wise scheduling
@return : list of scheduled working timing based on resource calendar.
TDE NOTE: mrp_operations/mrp_operations.py, crm/crm_lead.py
"""
res = self.interval_get_multi(cr, uid, [(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)], resource, byday)[(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)]
return res
@ -233,6 +536,8 @@ class resource_calendar(osv.osv):
@param context: current request context
@return : Total number of working hours based dt_from and dt_end and
resource if supplied.
TDE NOTE: used in project_issue/project_issue.py
"""
utc_tz = pytz.timezone('UTC')
local_tz = utc_tz
@ -374,6 +679,8 @@ class resource_resource(osv.osv):
def generate_resources(self, cr, uid, user_ids, calendar_id, context=None):
"""
Return a list of Resource Class objects for the resources allocated to the phase.
TDE NOTE: used in project/project.py
"""
resource_objs = {}
user_pool = self.pool.get('res.users')
@ -401,6 +708,8 @@ class resource_resource(osv.osv):
@param calendar_id : working calendar of the project
@param resource_id : resource working on phase/task
@param resource_calendar : working calendar of the resource
TDE NOTE: used in project/project.py, and in generate_resources
"""
resource_calendar_leaves_pool = self.pool.get('resource.calendar.leaves')
leave_list = []
@ -426,6 +735,8 @@ class resource_resource(osv.osv):
"""
Change the format of working calendar from 'Openerp' format to bring it into 'Faces' format.
@param calendar_id : working calendar of the project
TDE NOTE: used in project/project.py
"""
if not calendar_id:
# Calendar is not specified: working days: 24/7

View File

@ -253,5 +253,6 @@
</record>
<menuitem id="menu_resource_config" name="Resource" parent="base.next_id_4" sequence="5"/>
<menuitem action="resource.action_resource_calendar_leave_tree" id="menu_view_resource_calendar_leaves_search" parent="menu_resource_config" sequence="1"/>
<menuitem action="resource.action_resource_calendar_form" id="menu_view_resource_calendar_search" parent="menu_resource_config" sequence="1"/>
</data>
</openerp>

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.resource.tests import test_resource
checks = [
test_resource,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from openerp.tests import common
class TestResourceCommon(common.TransactionCase):
def setUp(self):
super(TestResourceCommon, self).setUp()
cr, uid = self.cr, self.uid
if not hasattr(self, 'context'):
self.context = {}
# Usefull models
self.resource_resource = self.registry('resource.resource')
self.resource_calendar = self.registry('resource.calendar')
self.resource_attendance = self.registry('resource.calendar.attendance')
self.resource_leaves = self.registry('resource.calendar.leaves')
# Some demo data
self.date1 = datetime.strptime('2013-02-12 09:22:13', '%Y-%m-%d %H:%M:%S') # weekday() returns 1, isoweekday() returns 2
self.date2 = datetime.strptime('2013-02-15 09:22:13', '%Y-%m-%d %H:%M:%S') # weekday() returns 4, isoweekday() returns 5
# Leave1: 19/02/2013, from 9 to 12, is a day 0
self.leave1_start = datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S')
self.leave1_end = datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S')
# Leave2: 22/02/2013, from 9 to 15, is a day 3
self.leave2_start = datetime.strptime('2013-02-22 09:00:00', '%Y-%m-%d %H:%M:%S')
self.leave2_end = datetime.strptime('2013-02-22 15:00:00', '%Y-%m-%d %H:%M:%S')
# Leave3: 25/02/2013 (day6) -> 01/03/2013 (day3)
self.leave3_start = datetime.strptime('2013-02-25 13:00:00', '%Y-%m-%d %H:%M:%S')
self.leave3_end = datetime.strptime('2013-03-01 11:30:00', '%Y-%m-%d %H:%M:%S')
# Resource data
# Calendar working days: 1 (8-16), 4 (8-13, 21-22)
self.calendar_id = self.resource_calendar.create(
cr, uid, {
'name': 'TestCalendar',
}
)
self.att1_id = self.resource_attendance.create(
cr, uid, {
'name': 'Att1',
'dayofweek': '1',
'hour_from': 8,
'hour_to': 16,
'calendar_id': self.calendar_id,
}
)
self.att2_id = self.resource_attendance.create(
cr, uid, {
'name': 'Att2',
'dayofweek': '4',
'hour_from': 8,
'hour_to': 13,
'calendar_id': self.calendar_id,
}
)
self.att3_id = self.resource_attendance.create(
cr, uid, {
'name': 'Att3',
'dayofweek': '4',
'hour_from': 16,
'hour_to': 23,
'calendar_id': self.calendar_id,
}
)
self.resource1_id = self.resource_resource.create(
cr, uid, {
'name': 'TestResource1',
'resource_type': 'user',
'time_efficiency': 150.0,
'calendar_id': self.calendar_id,
}
)
self.leave1_id = self.resource_leaves.create(
cr, uid, {
'name': 'GenericLeave',
'calendar_id': self.calendar_id,
'date_from': self.leave1_start,
'date_to': self.leave1_end,
}
)
self.leave2_id = self.resource_leaves.create(
cr, uid, {
'name': 'ResourceLeave',
'calendar_id': self.calendar_id,
'resource_id': self.resource1_id,
'date_from': self.leave2_start,
'date_to': self.leave2_end,
}
)
self.leave3_id = self.resource_leaves.create(
cr, uid, {
'name': 'ResourceLeave2',
'calendar_id': self.calendar_id,
'resource_id': self.resource1_id,
'date_from': self.leave3_start,
'date_to': self.leave3_end,
}
)
# Some browse data
self.calendar = self.resource_calendar.browse(cr, uid, self.calendar_id)

View File

@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from dateutil.relativedelta import relativedelta
from openerp.addons.resource.tests.common import TestResourceCommon
class TestResource(TestResourceCommon):
def test_00_intervals(self):
intervals = [
(
datetime.strptime('2013-02-04 09:00:00', '%Y-%m-%d %H:%M:%S'),
datetime.strptime('2013-02-04 11:00:00', '%Y-%m-%d %H:%M:%S')
), (
datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'),
datetime.strptime('2013-02-04 12:00:00', '%Y-%m-%d %H:%M:%S')
), (
datetime.strptime('2013-02-04 11:00:00', '%Y-%m-%d %H:%M:%S'),
datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S')
), (
datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'),
datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S')
), (
datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'),
datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S')
), (
datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S'),
datetime.strptime('2013-02-04 19:00:00', '%Y-%m-%d %H:%M:%S')
)
]
# Test: interval cleaning
result = self.resource_calendar.interval_clean(intervals)
self.assertEqual(len(result), 3, 'resource_calendar: wrong interval cleaning')
# First interval: 03, unchanged
self.assertEqual(result[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
self.assertEqual(result[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
# Second intreval: 04, 08-14, combining 08-12 and 11-14, 09-11 being inside 08-12
self.assertEqual(result[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
self.assertEqual(result[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
# Third interval: 04, 17-21, 18-19 being inside 17-21
self.assertEqual(result[2][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
self.assertEqual(result[2][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
# Test: disjoint removal
working_interval = (datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S'))
result = self.resource_calendar.interval_remove_leaves(working_interval, intervals)
self.assertEqual(len(result), 1, 'resource_calendar: wrong leave removal from interval')
# First interval: 04, 14-17
self.assertEqual(result[0][0], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
self.assertEqual(result[0][1], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
def test_10_calendar_basics(self):
""" Testing basic method of resource.calendar """
cr, uid = self.cr, self.uid
# --------------------------------------------------
# Test1: get_next_day
# --------------------------------------------------
# Test: next day: next day after day1 is day4
date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1)
self.assertEqual(date, self.date2, 'resource_calendar: wrong next day computing')
# Test: next day: next day after day4 is (day1+7)
date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2)
self.assertEqual(date, self.date1 + relativedelta(days=7), 'resource_calendar: wrong next day computing')
# Test: next day: next day after day4+1 is (day1+7)
date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2 + relativedelta(days=1))
self.assertEqual(date, self.date1 + relativedelta(days=7), 'resource_calendar: wrong next day computing')
# Test: next day: next day after day1-1 is day1
date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=-1))
self.assertEqual(date, self.date1, 'resource_calendar: wrong next day computing')
# --------------------------------------------------
# Test2: get_previous_day
# --------------------------------------------------
# Test: previous day: previous day before day1 is (day4-7)
date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1)
self.assertEqual(date, self.date2 + relativedelta(days=-7), 'resource_calendar: wrong previous day computing')
# Test: previous day: previous day before day4 is day1
date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2)
self.assertEqual(date, self.date1, 'resource_calendar: wrong previous day computing')
# Test: previous day: previous day before day4+1 is day4
date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2 + relativedelta(days=1))
self.assertEqual(date, self.date2, 'resource_calendar: wrong previous day computing')
# Test: previous day: previous day before day1-1 is (day4-7)
date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=-1))
self.assertEqual(date, self.date2 + relativedelta(days=-7), 'resource_calendar: wrong previous day computing')
def test_20_calendar_working_intervals(self):
""" Testing working intervals computing method of resource.calendar """
cr, uid = self.cr, self.uid
# Test: day0 without leaves: 1 interval
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1)
self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
# Test: day3 without leaves: 2 interval
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date2)
self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[1][0], datetime.strptime('2013-02-15 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
# Test: day0 with leaves outside range: 1 interval
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1, compute_leaves=True)
self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
# Test: day0 with leaves: 2 intrevals because of leave between 9 ans 12
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=7), compute_leaves=True)
self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals')
def test_40_calendar_schedule_days(self):
""" Testing calendar days scheduling """
cr, uid = self.cr, self.uid
print '---------------'
res = self.resource_calendar.schedule_days(cr, uid, self.calendar_id, 5, date=self.date1)
print res
# --------------------------------------------------
# Misc
# --------------------------------------------------
# Without calendar, should only count days
print '---------------'
res = self.resource_calendar.schedule_days(cr, uid, None, 5, date=self.date1)
print res
# @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
# def test_20_calendar(self):
# """ Testing calendar and time computation """
# cr, uid = self.cr, self.uid
# wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, day_date=self.date1)
# self.assertEqual(wh, 8, 'cacamou')
# wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, day_date=self.date2+relativedelta(days=7))
# self.assertEqual(wh, 12, 'cacamou')
# # print '---------------------'
# # print self.date1
# # res = self.resource_calendar.interval_min_get(cr, uid, self.calendar_id, self.date1, 40, resource=False)
# # print res
# print '----------------------'
# res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, 40, start_datetime=self.date1)
# print res
# print '----------------------'
# # print self.date1
# # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True)
# # print res