[MERGE] [REF] [IMP] base_action_rule: allow using a resource.calendar to compute
day-based timing conditions. [REF] resource: cleaned, refactored and improved code for resource.calendar module. Old API is kept for backward compatibility. [TESTS] resource: added tests for the new implementation of the various methods. [IMP] base_action_rule: using the resource module, added the possibility to specify a resource.calendar to compute day-based timing conditions. [IMP] hr_contract: added an override of base action rule to add the possibility of using an employees's resource.calendar to compute day-based timing conditions. bzr revid: tde@openerp.com-20140122150032-uu6mk8s0arachzy4
This commit is contained in:
commit
7dd432cd5d
|
@ -35,7 +35,7 @@ trigger an automatic reminder email.
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base', 'mail'],
|
||||
'depends': ['base', 'resource', 'mail'],
|
||||
'data': [
|
||||
'base_action_rule_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
|
|
@ -78,6 +78,11 @@ class base_action_rule(osv.osv):
|
|||
"trigger date, like sending a reminder 15 minutes before a meeting."),
|
||||
'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'),
|
||||
('day', 'Days'), ('month', 'Months')], 'Delay type'),
|
||||
'trg_date_calendar_id': fields.many2one(
|
||||
'resource.calendar', 'Use Calendar',
|
||||
help='When calculating a day-based timed condition, it is possible to use a calendar to compute the date based on working days.',
|
||||
ondelete='set null',
|
||||
),
|
||||
'act_user_id': fields.many2one('res.users', 'Set Responsible'),
|
||||
'act_followers': fields.many2many("res.partner", string="Add Followers"),
|
||||
'server_action_ids': fields.many2many('ir.actions.server', string='Server Actions',
|
||||
|
@ -241,6 +246,18 @@ class base_action_rule(osv.osv):
|
|||
data.update({'model': model.model})
|
||||
return {'value': data}
|
||||
|
||||
def _check_delay(self, cr, uid, action, record, record_dt, context=None):
|
||||
if action.trg_date_calendar_id and action.trg_date_range_type == 'day':
|
||||
start_dt = get_datetime(record_dt)
|
||||
action_dt = self.pool['resource.calendar'].schedule_days_get_date(
|
||||
cr, uid, action.trg_date_calendar_id.id, action.trg_date_range,
|
||||
day_date=start_dt, compute_leaves=True, context=context
|
||||
)
|
||||
else:
|
||||
delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range)
|
||||
action_dt = get_datetime(record_dt) + delay
|
||||
return action_dt
|
||||
|
||||
def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
|
||||
""" This Function is called by scheduler. """
|
||||
context = context or {}
|
||||
|
@ -267,14 +284,12 @@ class base_action_rule(osv.osv):
|
|||
else:
|
||||
get_record_dt = lambda record: record[date_field]
|
||||
|
||||
delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range)
|
||||
|
||||
# process action on the records that should be executed
|
||||
for record in model.browse(cr, uid, record_ids, context=context):
|
||||
record_dt = get_record_dt(record)
|
||||
if not record_dt:
|
||||
continue
|
||||
action_dt = get_datetime(record_dt) + delay
|
||||
action_dt = self._check_delay(cr, uid, action, record, record_dt, context=context)
|
||||
if last_run and (last_run <= action_dt < now) or (action_dt < now):
|
||||
try:
|
||||
self._process(cr, uid, action, [record.id], context=context)
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
<field name="trg_date_range" class="oe_inline"/>
|
||||
<field name="trg_date_range_type" class="oe_inline" attrs="{'required': [('trg_date_id','!=',False)]}"/>
|
||||
</div>
|
||||
<field name="trg_date_calendar_id" class="oe_inline"
|
||||
attrs="{'invisible': ['|', ('trg_date_id','=',False), ('trg_date_range_type', '!=', 'day')]}"/>
|
||||
</group>
|
||||
<p>
|
||||
Select when the action must be run, and add filters and/or timing conditions.
|
||||
|
|
|
@ -316,9 +316,6 @@
|
|||
groups="base.group_no_one" sequence="15"
|
||||
parent="base.menu_base_config"/>
|
||||
|
||||
<!-- menu for the working time -->
|
||||
<menuitem action="resource.action_resource_calendar_form" id="menu_action_resource_calendar_form" parent="resource.menu_resource_config" sequence="1"/>
|
||||
|
||||
<!-- Payment Mode Tree View -->
|
||||
|
||||
<record model="ir.ui.view" id="view_crm_payment_mode_tree">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-Today OpenERP SA (<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
|
||||
|
@ -20,6 +20,7 @@
|
|||
##############################################################################
|
||||
|
||||
import hr_contract
|
||||
import base_action_rule
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -38,11 +38,12 @@ You can assign several contracts per employee.
|
|||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/hr_contract.jpeg'],
|
||||
'depends': ['hr'],
|
||||
'depends': ['base_action_rule', 'hr'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'hr_contract_view.xml',
|
||||
'hr_contract_data.xml'
|
||||
'hr_contract_data.xml',
|
||||
'base_action_rule_view.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'test': ['test/test_hr_contract.yml'],
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013 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.base_action_rule.base_action_rule import get_datetime
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
|
||||
class base_action_rule(osv.Model):
|
||||
""" Add resource and calendar for time-based conditions """
|
||||
_name = 'base.action.rule'
|
||||
_inherit = ['base.action.rule']
|
||||
|
||||
_columns = {
|
||||
'trg_date_resource_field_id': fields.many2one(
|
||||
'ir.model.fields', 'Use employee work schedule',
|
||||
help='Use the user\'s working schedule.',
|
||||
),
|
||||
}
|
||||
|
||||
def _check_delay(self, cr, uid, action, record, record_dt, context=None):
|
||||
""" Override the check of delay to try to use a user-related calendar.
|
||||
If no calendar is found, fallback on the default behavior. """
|
||||
if action.trg_date_calendar_id and action.trg_date_range_type == 'day' and action.trg_date_resource_field_id:
|
||||
user = record[action.trg_date_resource_field_id.name]
|
||||
if user.employee_ids and user.employee_ids[0].contract_id \
|
||||
and user.employee_ids[0].contract_id.working_hours:
|
||||
calendar = user.employee_ids[0].contract_id.working_hours
|
||||
start_dt = get_datetime(record_dt)
|
||||
resource_id = user.employee_ids[0].resource_id.id
|
||||
action_dt = self.pool['resource.calendar'].schedule_days_get_date(
|
||||
cr, uid, calendar.id, action.trg_date_range,
|
||||
day_date=start_dt, compute_leaves=True, resource_id=resource_id,
|
||||
context=context
|
||||
)
|
||||
return action_dt
|
||||
return super(base_action_rule, self)._check_delay(cr, uid, action, record, record_dt, context=context)
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="view_base_action_rule_form_resource">
|
||||
<field name="name">base.action.rule.form</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='trg_date_calendar_id']" position="before">
|
||||
<field name="trg_date_resource_field_id"
|
||||
domain="[('model_id', '=', model_id), ('relation', '=', 'res.users'), ('ttype', 'in', ['many2one'])]"
|
||||
attrs="{'invisible': ['|', ('trg_date_id','=',False), ('trg_date_range_type', '!=', 'day')]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -39,6 +39,7 @@ access_res_partner,res.partner,base.model_res_partner,mrp.group_mrp_user,1,0,0,0
|
|||
access_workcenter_user,mrp.production.workcenter.line.user,model_mrp_production_workcenter_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_leaves_user,mrp.resource.calendar.leaves.user,resource.model_resource_calendar_leaves,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_leaves_manager,mrp.resource.calendar.leaves.manager,resource.model_resource_calendar_leaves,mrp.group_mrp_manager,1,0,0,0
|
||||
access_resource_calendar_attendance_mrp_user,mrp.resource.calendar.attendance.mrp.user,resource.model_resource_calendar_attendance,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_attendance_manager,mrp.resource.calendar.attendance.manager,resource.model_resource_calendar_attendance,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_puom_categ,product.uom.categ,product.model_product_uom_categ,mrp.group_mrp_user,1,0,0,0
|
||||
access_resource_resource,resource.resource,resource.model_resource_resource,mrp.group_mrp_user,1,0,0,0
|
||||
|
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-TODAY OpenERP SA (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
|
||||
|
@ -19,304 +19,613 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
import datetime
|
||||
from dateutil import rrule
|
||||
import math
|
||||
from dateutil.relativedelta import relativedelta
|
||||
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):
|
||||
""" Calendar model for a resource. It has
|
||||
|
||||
- attendance_ids: list of resource.calendar.attendance that are a working
|
||||
interval in a given weekday.
|
||||
- leave_ids: list of leaves linked to this calendar. A leave can be general
|
||||
or linked to a specific resource, depending on its resource_id.
|
||||
|
||||
All methods in this class use intervals. An interval is a tuple holding
|
||||
(begin_datetime, end_datetime). A list of intervals is therefore a list of
|
||||
tuples, holding several intervals of work or leaves. """
|
||||
_name = "resource.calendar"
|
||||
_description = "Resource Calendar"
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char("Name", size=64, required=True),
|
||||
'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'),
|
||||
'name': fields.char("Name", size=64, required=True),
|
||||
'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 sorts and removes overlapping inside datetime
|
||||
intervals. The intervals are sorted based on increasing starting datetime.
|
||||
Overlapping intervals are merged into a single one.
|
||||
|
||||
@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
|
||||
"""
|
||||
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
|
||||
:param list intervals: list of intervals; each interval is a tuple
|
||||
(datetime_from, datetime_to)
|
||||
:return list cleaned: list of sorted intervals 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]]
|
||||
elif working_interval[1] < current_interval[0]: # interval is disjoint
|
||||
cleaned.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
|
||||
cleaned.append(tuple(working_interval))
|
||||
return cleaned
|
||||
|
||||
def _get_leaves(self, cr, uid, id, resource):
|
||||
"""Private Method to Calculate resource Leaves days
|
||||
def interval_remove_leaves(self, interval, leave_intervals):
|
||||
""" Utility method that remove leave intervals from a base interval:
|
||||
|
||||
@param id: resource calendar id
|
||||
@param resource: resource id for which leaves will ew calculated
|
||||
- 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:
|
||||
|
||||
@return : returns the list of dates, where resource on leave in
|
||||
resource.calendar.leaves object (e.g.['%Y-%m-%d', '%Y-%m-%d'])
|
||||
"""
|
||||
resource_cal_leaves = self.pool.get('resource.calendar.leaves')
|
||||
dt_leave = []
|
||||
resource_leave_ids = resource_cal_leaves.search(cr, uid, [('calendar_id','=',id), '|', ('resource_id','=',False), ('resource_id','=',resource)])
|
||||
#res_leaves = resource_cal_leaves.read(cr, uid, resource_leave_ids, ['date_from', 'date_to'])
|
||||
res_leaves = resource_cal_leaves.browse(cr, uid, resource_leave_ids)
|
||||
- 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
|
||||
|
||||
for leave in res_leaves:
|
||||
dtf = datetime.strptime(leave.date_from, '%Y-%m-%d %H:%M:%S')
|
||||
dtt = datetime.strptime(leave.date_to, '%Y-%m-%d %H:%M:%S')
|
||||
no = dtt - dtf
|
||||
[dt_leave.append((dtf + timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
|
||||
dt_leave.sort()
|
||||
|
||||
return dt_leave
|
||||
|
||||
def interval_min_get(self, cr, uid, id, dt_from, hours, resource=False):
|
||||
"""
|
||||
Calculates the working Schedule from supplied from date to till hours
|
||||
will be satisfied based or resource calendar id. If resource is also
|
||||
given then it will consider the resource leave also and than will
|
||||
calculates resource working schedule
|
||||
|
||||
@param dt_from: datetime object, start of working scheduled
|
||||
@param hours: float, total number working hours needed scheduled from
|
||||
start date
|
||||
@param resource : Optional Resource id, if supplied than resource leaves
|
||||
will also taken into consideration for calculating working
|
||||
schedule.
|
||||
@return : List datetime object of working schedule based on supplies
|
||||
params
|
||||
"""
|
||||
if not id:
|
||||
td = int(hours)*3
|
||||
return [(dt_from - timedelta(hours=td), dt_from)]
|
||||
dt_leave = self._get_leaves(cr, uid, id, resource)
|
||||
dt_leave.reverse()
|
||||
todo = hours
|
||||
result = []
|
||||
maxrecur = 100
|
||||
current_hour = dt_from.hour
|
||||
while float_compare(todo, 0, 4) and maxrecur:
|
||||
cr.execute("select hour_from,hour_to from resource_calendar_attendance where dayofweek='%s' and calendar_id=%s order by hour_from desc", (dt_from.weekday(),id))
|
||||
for (hour_from,hour_to) in cr.fetchall():
|
||||
leave_flag = False
|
||||
if (hour_from<current_hour) and float_compare(todo, 0, 4):
|
||||
m = min(hour_to, current_hour)
|
||||
if (m-hour_from)>todo:
|
||||
hour_from = m-todo
|
||||
dt_check = dt_from.strftime('%Y-%m-%d')
|
||||
for leave in dt_leave:
|
||||
if dt_check == leave:
|
||||
dt_check = datetime.strptime(dt_check, '%Y-%m-%d') + timedelta(days=1)
|
||||
leave_flag = True
|
||||
if leave_flag:
|
||||
break
|
||||
else:
|
||||
d1 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_from)), int((hour_from%1) * 60))
|
||||
d2 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60))
|
||||
result.append((d1, d2))
|
||||
current_hour = hour_from
|
||||
todo -= (m-hour_from)
|
||||
dt_from -= timedelta(days=1)
|
||||
current_hour = 24
|
||||
maxrecur -= 1
|
||||
result.reverse()
|
||||
return result
|
||||
|
||||
# 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):
|
||||
def group(lst, key):
|
||||
lst.sort(key=itemgetter(key))
|
||||
grouped = groupby(lst, itemgetter(key))
|
||||
return dict([(k, [v for v in itr]) for k, itr in grouped])
|
||||
# END group
|
||||
|
||||
cr.execute("select calendar_id, dayofweek, hour_from, hour_to from resource_calendar_attendance order by hour_from")
|
||||
hour_res = cr.dictfetchall()
|
||||
hours_by_cal = group(hour_res, 'calendar_id')
|
||||
|
||||
results = {}
|
||||
|
||||
for d, hours, id in date_and_hours_by_cal:
|
||||
dt_from = datetime.strptime(d, '%Y-%m-%d %H:%M:%S')
|
||||
if not id:
|
||||
td = int(hours)*3
|
||||
results[(d, hours, id)] = [(dt_from, dt_from + timedelta(hours=td))]
|
||||
: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 = []
|
||||
intervals = []
|
||||
leave_intervals = self.interval_clean(leave_intervals)
|
||||
current_interval = [interval[0], interval[1]]
|
||||
for leave in leave_intervals:
|
||||
if leave[1] <= current_interval[0]:
|
||||
continue
|
||||
if leave[0] >= current_interval[1]:
|
||||
break
|
||||
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]]
|
||||
# if current_interval[0] <= leave[1] <= current_interval[1]:
|
||||
if current_interval[0] <= leave[1]:
|
||||
current_interval[0] = leave[1]
|
||||
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]))
|
||||
return intervals
|
||||
|
||||
dt_leave = self._get_leaves(cr, uid, id, resource)
|
||||
todo = hours
|
||||
result = []
|
||||
maxrecur = 100
|
||||
current_hour = dt_from.hour
|
||||
while float_compare(todo, 0, 4) and maxrecur:
|
||||
for (hour_from,hour_to) in [(item['hour_from'], item['hour_to']) for item in hours_by_cal[id] if item['dayofweek'] == str(dt_from.weekday())]:
|
||||
leave_flag = False
|
||||
if (hour_to>current_hour) and float_compare(todo, 0, 4):
|
||||
m = max(hour_from, current_hour)
|
||||
if (hour_to-m)>todo:
|
||||
hour_to = m+todo
|
||||
dt_check = dt_from.strftime('%Y-%m-%d')
|
||||
for leave in dt_leave:
|
||||
if dt_check == leave:
|
||||
dt_check = datetime.strptime(dt_check, '%Y-%m-%d') + timedelta(days=1)
|
||||
leave_flag = True
|
||||
if leave_flag:
|
||||
break
|
||||
else:
|
||||
d1 = datetime(dt_from.year, dt_from.month, dt_from.day) + timedelta(hours=int(math.floor(m)), minutes=int((m%1) * 60))
|
||||
d2 = datetime(dt_from.year, dt_from.month, dt_from.day) + timedelta(hours=int(math.floor(hour_to)), minutes=int((hour_to%1) * 60))
|
||||
result.append((d1, d2))
|
||||
current_hour = hour_to
|
||||
todo -= (hour_to - m)
|
||||
dt_from += timedelta(days=1)
|
||||
current_hour = 0
|
||||
maxrecur -= 1
|
||||
results[(d, hours, id)] = result
|
||||
def interval_schedule_hours(self, intervals, hour, remove_at_end=True):
|
||||
""" Schedule hours in intervals. The last matching interval is truncated
|
||||
to match the specified hours.
|
||||
|
||||
It is possible to truncate the last interval at its beginning or ending.
|
||||
However this does nothing on the given interval order that should be
|
||||
submitted accordingly.
|
||||
|
||||
:param list intervals: a list of tuples (beginning datetime, ending datetime)
|
||||
:param int/float hours: number of hours to schedule. It will be converted
|
||||
into a timedelta, but should be submitted as an
|
||||
int or float.
|
||||
:param boolean remove_at_end: remove extra hours at the end of the last
|
||||
matching interval. Otherwise, do it at the
|
||||
beginning.
|
||||
|
||||
:return list results: a list of intervals. If the number of hours to schedule
|
||||
is greater than the possible scheduling in the intervals, no extra-scheduling
|
||||
is done, and results == intervals. """
|
||||
results = []
|
||||
res = datetime.timedelta()
|
||||
limit = datetime.timedelta(hours=hour)
|
||||
for interval in intervals:
|
||||
res += interval[1] - interval[0]
|
||||
if res > limit and remove_at_end:
|
||||
interval = (interval[0], interval[1] + relativedelta(seconds=(limit-res).total_seconds()))
|
||||
elif res > limit:
|
||||
interval = (interval[0] + relativedelta(seconds=(res-limit).total_seconds()), interval[1])
|
||||
results.append(interval)
|
||||
if res > limit:
|
||||
break
|
||||
return results
|
||||
|
||||
def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True):
|
||||
"""Calculates Resource Working Internal Timing Based on Resource Calendar.
|
||||
# --------------------------------------------------
|
||||
# Date and hours computation
|
||||
# --------------------------------------------------
|
||||
|
||||
@param dt_from: start resource schedule calculation.
|
||||
@param hours : total number of working hours to be scheduled.
|
||||
@param resource: optional resource id, If supplied it will take care of
|
||||
resource leave while scheduling.
|
||||
@param byday: boolean flag bit enforce day wise scheduling
|
||||
def get_attendances_for_weekdays(self, cr, uid, id, weekdays, context=None):
|
||||
""" Given a list of weekdays, return matching resource.calendar.attendance"""
|
||||
calendar = self.browse(cr, uid, id, context=None)
|
||||
return [att for att in calendar.attendance_ids if int(att.dayofweek) in weekdays]
|
||||
|
||||
@return : list of scheduled working timing based on resource calendar.
|
||||
def get_weekdays(self, cr, uid, id, default_weekdays=None, context=None):
|
||||
""" Return the list of weekdays that contain at least one working interval.
|
||||
If no id is given (no calendar), return default weekdays. """
|
||||
if id is None:
|
||||
return default_weekdays if default_weekdays is not None else [0, 1, 2, 3, 4]
|
||||
calendar = self.browse(cr, uid, id, context=None)
|
||||
weekdays = set()
|
||||
for attendance in calendar.attendance_ids:
|
||||
weekdays.add(int(attendance.dayofweek))
|
||||
return list(weekdays)
|
||||
|
||||
def get_next_day(self, cr, uid, id, day_date, context=None):
|
||||
""" Get following date of day_date, based on resource.calendar. If no
|
||||
calendar is provided, just return the next day.
|
||||
|
||||
:param int id: id of a resource.calendar. If not given, simply add one day
|
||||
to the submitted date.
|
||||
:param date day_date: current day as a date
|
||||
|
||||
:return date: next day of calendar, or just next day """
|
||||
if not id:
|
||||
return day_date + relativedelta(days=1)
|
||||
weekdays = self.get_weekdays(cr, uid, id, context)
|
||||
|
||||
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):
|
||||
""" Get previous date of day_date, based on resource.calendar. If no
|
||||
calendar is provided, just return the previous day.
|
||||
|
||||
:param int id: id of a resource.calendar. If not given, simply remove
|
||||
one day from the submitted date.
|
||||
:param date day_date: current day as a date
|
||||
|
||||
:return date: previous day of calendar, or just previous day """
|
||||
if not id:
|
||||
return day_date + relativedelta(days=-1)
|
||||
weekdays = self.get_weekdays(cr, uid, id, context)
|
||||
weekdays.reverse()
|
||||
|
||||
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: the id of the resource to take into account when
|
||||
computing the leaves. If not set, only general
|
||||
leaves are computed. If set, generic and
|
||||
specific leaves are computed.
|
||||
:param datetime start_datetime: if provided, do not take into account leaves
|
||||
ending before this date.
|
||||
:param datetime end_datetime: if provided, do not take into account leaves
|
||||
beginning after this date.
|
||||
|
||||
:return list leaves: list of tuples (start_datetime, end_datetime) of
|
||||
leave intervals
|
||||
"""
|
||||
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)]
|
||||
resource_calendar = self.browse(cr, uid, id, context=context)
|
||||
leaves = []
|
||||
for leave in resource_calendar.leave_ids:
|
||||
if leave.resource_id and not resource_id == leave.resource_id.id:
|
||||
continue
|
||||
date_from = datetime.datetime.strptime(leave.date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
if end_datetime and date_from > end_datetime:
|
||||
continue
|
||||
date_to = datetime.datetime.strptime(leave.date_to, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
if start_datetime and date_to < start_datetime:
|
||||
continue
|
||||
leaves.append((date_from, date_to))
|
||||
return leaves
|
||||
|
||||
def get_working_intervals_of_day(self, cr, uid, id, start_dt=None, end_dt=None,
|
||||
leaves=None, compute_leaves=False, resource_id=None,
|
||||
default_interval=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 datetime start_dt: datetime object that is the beginning hours
|
||||
for the working intervals computation; any
|
||||
working interval beginning before start_dt
|
||||
will be truncated. If not set, set to end_dt
|
||||
or today() if no end_dt at 00.00.00.
|
||||
:param datetime end_dt: datetime object that is the ending hour
|
||||
for the working intervals computation; any
|
||||
working interval ending after end_dt
|
||||
will be truncated. If not set, set to start_dt()
|
||||
at 23.59.59.
|
||||
: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 are computed. If set, generic and
|
||||
specific leaves are computed.
|
||||
:param tuple default_interval: if no id, try to return a default working
|
||||
day using default_interval[0] as beginning
|
||||
hour, and default_interval[1] as ending hour.
|
||||
Example: default_interval = (8, 16).
|
||||
Otherwise, a void list of working intervals
|
||||
is returned when id is None.
|
||||
|
||||
:return list intervals: a list of tuples (start_datetime, end_datetime)
|
||||
of work intervals """
|
||||
if isinstance(id, (list, tuple)):
|
||||
id = id[0]
|
||||
|
||||
# Computes start_dt, end_dt (with default values if not set) + off-interval work limits
|
||||
work_limits = []
|
||||
if start_dt is None and end_dt is not None:
|
||||
start_dt = end_dt.replace(hour=0, minute=0, second=0)
|
||||
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))
|
||||
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)))
|
||||
assert start_dt.date() == end_dt.date(), 'get_working_intervals_of_day is restricted to one day'
|
||||
|
||||
intervals = []
|
||||
work_dt = start_dt.replace(hour=0, minute=0, second=0)
|
||||
|
||||
# no calendar: try to use the default_interval, then return directly
|
||||
if id is None:
|
||||
if default_interval:
|
||||
intervals.append((start_dt.replace(hour=default_interval[0]), start_dt.replace(hour=default_interval[1])))
|
||||
return intervals
|
||||
|
||||
working_intervals = []
|
||||
for calendar_working_day in self.get_attendances_for_weekdays(cr, uid, id, [start_dt.weekday()], context):
|
||||
working_interval = (
|
||||
work_dt.replace(hour=int(calendar_working_day.hour_from)),
|
||||
work_dt.replace(hour=int(calendar_working_day.hour_to))
|
||||
)
|
||||
working_intervals += self.interval_remove_leaves(working_interval, work_limits)
|
||||
|
||||
# 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, start_dt=None, end_dt=None,
|
||||
leaves=None, compute_leaves=False, resource_id=None,
|
||||
default_interval=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 = datetime.timedelta()
|
||||
intervals = self.get_working_intervals_of_day(
|
||||
cr, uid, id,
|
||||
start_dt, end_dt, leaves,
|
||||
compute_leaves, resource_id,
|
||||
default_interval, context)
|
||||
for interval in intervals:
|
||||
res += interval[1] - interval[0]
|
||||
return (res.total_seconds() / 3600.0)
|
||||
|
||||
def get_working_hours(self, cr, uid, id, start_dt, end_dt, compute_leaves=False,
|
||||
resource_id=None, default_interval=None, context=None):
|
||||
hours = 0.0
|
||||
for day in rrule.rrule(rrule.DAILY, dtstart=start_dt,
|
||||
until=end_dt + datetime.timedelta(days=1),
|
||||
byweekday=self.get_weekdays(cr, uid, id, context=context)):
|
||||
hours += self.get_working_hours_of_date(
|
||||
cr, uid, id, start_dt=day,
|
||||
compute_leaves=compute_leaves, resource_id=resource_id,
|
||||
default_interval=default_interval,
|
||||
context=context)
|
||||
return hours
|
||||
|
||||
# --------------------------------------------------
|
||||
# Hours scheduling
|
||||
# --------------------------------------------------
|
||||
|
||||
def _schedule_hours(self, cr, uid, id, hours, day_dt=None,
|
||||
compute_leaves=False, resource_id=None,
|
||||
default_interval=None, context=None):
|
||||
""" Schedule hours of work, using a calendar and an optional resource to
|
||||
compute working and leave days. This method can be used backwards, i.e.
|
||||
scheduling days before a deadline.
|
||||
|
||||
:param int hours: number of hours to schedule. Use a negative number to
|
||||
compute a backwards scheduling.
|
||||
:param datetime day_dt: reference date to compute working days. If days is
|
||||
> 0 date is the starting date. If days is < 0
|
||||
date is the ending date.
|
||||
:param boolean compute_leaves: if set, compute the leaves based on calendar
|
||||
and resource. Otherwise 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 are computed. If set, generic and
|
||||
specific leaves are computed.
|
||||
:param tuple default_interval: if no id, try to return a default working
|
||||
day using default_interval[0] as beginning
|
||||
hour, and default_interval[1] as ending hour.
|
||||
Example: default_interval = (8, 16).
|
||||
Otherwise, a void list of working intervals
|
||||
is returned when id is None.
|
||||
|
||||
:return tuple (datetime, intervals): datetime is the beginning/ending date
|
||||
of the schedulign; intervals are the
|
||||
working intervals of the scheduling.
|
||||
|
||||
Note: Why not using rrule.rrule ? Because rrule does not seem to allow
|
||||
getting back in time.
|
||||
"""
|
||||
if day_dt is None:
|
||||
day_dt = datetime.datetime.now()
|
||||
backwards = (hours < 0)
|
||||
hours = abs(hours)
|
||||
intervals = []
|
||||
remaining_hours = hours * 1.0
|
||||
iterations = 0
|
||||
current_datetime = day_dt
|
||||
|
||||
call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id, default_interval=default_interval, context=context)
|
||||
|
||||
while float_compare(remaining_hours, 0.0, precision_digits=2) in (1, 0) and iterations < 1000:
|
||||
if backwards:
|
||||
call_args['end_dt'] = current_datetime
|
||||
else:
|
||||
call_args['start_dt'] = current_datetime
|
||||
|
||||
working_intervals = self.get_working_intervals_of_day(cr, uid, id, **call_args)
|
||||
|
||||
if id is None and not working_interval: # no calendar -> consider working 8 hours
|
||||
remaining_hours -= 8.0
|
||||
elif working_intervals:
|
||||
if backwards:
|
||||
working_intervals.reverse()
|
||||
new_working_intervals = self.interval_schedule_hours(working_intervals, remaining_hours, not backwards)
|
||||
if backwards:
|
||||
new_working_intervals.reverse()
|
||||
|
||||
res = datetime.timedelta()
|
||||
for interval in working_intervals:
|
||||
res += interval[1] - interval[0]
|
||||
remaining_hours -= (res.total_seconds() / 3600.0)
|
||||
if backwards:
|
||||
intervals = new_working_intervals + intervals
|
||||
else:
|
||||
intervals = intervals + new_working_intervals
|
||||
# get next day
|
||||
if backwards:
|
||||
current_datetime = datetime.datetime.combine(self.get_previous_day(cr, uid, id, current_datetime, context), datetime.time(23, 59, 59))
|
||||
else:
|
||||
current_datetime = datetime.datetime.combine(self.get_next_day(cr, uid, id, current_datetime, context), datetime.time())
|
||||
# avoid infinite loops
|
||||
iterations += 1
|
||||
|
||||
return intervals
|
||||
|
||||
def schedule_hours_get_date(self, cr, uid, id, hours, day_dt=None,
|
||||
compute_leaves=False, resource_id=None,
|
||||
default_interval=None, context=None):
|
||||
""" Wrapper on _schedule_hours: return the beginning/ending datetime of
|
||||
an hours scheduling. """
|
||||
res = self._schedule_hours(cr, uid, id, hours, day_dt, compute_leaves, resource_id, default_interval, context)
|
||||
return res and res[0][0] or False
|
||||
|
||||
def schedule_hours(self, cr, uid, id, hours, day_dt=None,
|
||||
compute_leaves=False, resource_id=None,
|
||||
default_interval=None, context=None):
|
||||
""" Wrapper on _schedule_hours: return the working intervals of an hours
|
||||
scheduling. """
|
||||
return self._schedule_hours(cr, uid, id, hours, day_dt, compute_leaves, resource_id, default_interval, context)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Days scheduling
|
||||
# --------------------------------------------------
|
||||
|
||||
def _schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False,
|
||||
resource_id=None, default_interval=None, context=None):
|
||||
"""Schedule days of work, using a calendar and an optional resource to
|
||||
compute working and leave days. This method can be used backwards, i.e.
|
||||
scheduling days before a deadline.
|
||||
|
||||
:param int days: number of days to schedule. Use a negative number to
|
||||
compute a backwards scheduling.
|
||||
:param date day_date: reference date to compute working days. If days is > 0
|
||||
date is the starting date. If days is < 0 date is the
|
||||
ending date.
|
||||
:param boolean compute_leaves: if set, compute the leaves based on calendar
|
||||
and resource. Otherwise 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 are computed. If set, generic and
|
||||
specific leaves are computed.
|
||||
:param tuple default_interval: if no id, try to return a default working
|
||||
day using default_interval[0] as beginning
|
||||
hour, and default_interval[1] as ending hour.
|
||||
Example: default_interval = (8, 16).
|
||||
Otherwise, a void list of working intervals
|
||||
is returned when id is None.
|
||||
|
||||
:return tuple (datetime, intervals): datetime is the beginning/ending date
|
||||
of the schedulign; intervals are the
|
||||
working intervals of the scheduling.
|
||||
|
||||
Implementation note: rrule.rrule is not used because rrule it des not seem
|
||||
to allow getting back in time.
|
||||
"""
|
||||
if day_date is None:
|
||||
day_date = datetime.datetime.now()
|
||||
backwards = (days < 0)
|
||||
days = abs(days)
|
||||
intervals = []
|
||||
planned_days = 0
|
||||
iterations = 0
|
||||
if backwards:
|
||||
current_datetime = day_date.replace(hour=23, minute=59, second=59)
|
||||
else:
|
||||
current_datetime = day_date.replace(hour=0, minute=0, second=0)
|
||||
|
||||
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,
|
||||
default_interval=default_interval,
|
||||
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, context)
|
||||
else:
|
||||
current_datetime = self.get_next_day(cr, uid, id, current_datetime, context)
|
||||
# avoid infinite loops
|
||||
iterations += 1
|
||||
|
||||
return intervals
|
||||
|
||||
def schedule_days_get_date(self, cr, uid, id, days, day_date=None, compute_leaves=False,
|
||||
resource_id=None, default_interval=None, context=None):
|
||||
""" Wrapper on _schedule_days: return the beginning/ending datetime of
|
||||
a days scheduling. """
|
||||
res = self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, default_interval, context)
|
||||
return res and res[-1][1] or False
|
||||
|
||||
def schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False,
|
||||
resource_id=None, default_interval=None, context=None):
|
||||
""" Wrapper on _schedule_days: return the working intervals of a days
|
||||
scheduling. """
|
||||
return self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, default_interval, context)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Compatibility / to clean / to remove
|
||||
# --------------------------------------------------
|
||||
|
||||
def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None):
|
||||
""" Used in hr_payroll/hr_payroll.py
|
||||
|
||||
:deprecated: OpenERP saas-3. Use get_working_hours_of_date instead. Note:
|
||||
since saas-3, take hour/minutes into account, not just the whole day."""
|
||||
if isinstance(day, datetime.datetime):
|
||||
day = day.replace(hour=0, minute=0)
|
||||
return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, start_dt=day, context=None)
|
||||
|
||||
def interval_min_get(self, cr, uid, id, dt_from, hours, resource=False):
|
||||
""" Schedule hours backwards. Used in mrp_operations/mrp_operations.py.
|
||||
|
||||
:deprecated: OpenERP saas-3. Use schedule_hours instead. Note: since
|
||||
saas-3, counts leave hours instead of all-day leaves."""
|
||||
return self.schedule_hours(
|
||||
cr, uid, id, hours * -1.0,
|
||||
day_dt=dt_from.replace(minute=0, second=0),
|
||||
compute_leaves=True, resource_id=resource,
|
||||
default_interval=(8, 16)
|
||||
)
|
||||
|
||||
def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True):
|
||||
""" Used in mrp_operations/mrp_operations.py (default parameters) and in
|
||||
interval_get()
|
||||
|
||||
:deprecated: OpenERP saas-3. Use schedule_hours instead. Note:
|
||||
Byday was not used. Since saas-3, counts Leave hours instead of all-day leaves."""
|
||||
res = {}
|
||||
for dt_str, hours, calendar_id in date_and_hours_by_cal:
|
||||
result = self.schedule_hours(
|
||||
cr, uid, calendar_id, hours,
|
||||
day_dt=datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(minute=0, second=0),
|
||||
compute_leaves=True, resource_id=resource,
|
||||
default_interval=(8, 16)
|
||||
)
|
||||
res[(dt_str, hours, calendar_id)] = result
|
||||
return res
|
||||
|
||||
def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True):
|
||||
""" Unifier of interval_get_multi. Used in: mrp_operations/mrp_operations.py,
|
||||
crm/crm_lead.py (res given).
|
||||
|
||||
:deprecated: OpenERP saas-3. Use get_working_hours instead."""
|
||||
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
|
||||
|
||||
def interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource=False):
|
||||
""" Calculates the Total Working hours based on given start_date to
|
||||
end_date, If resource id is supplied that it will consider the source
|
||||
leaves also in calculating the hours.
|
||||
""" Unused wrapper.
|
||||
|
||||
@param dt_from : date start to calculate hours
|
||||
@param dt_end : date end to calculate hours
|
||||
@param resource: optional resource id, If given resource leave will be
|
||||
considered.
|
||||
|
||||
@return : Total number of working hours based dt_from and dt_end and
|
||||
resource if supplied.
|
||||
"""
|
||||
:deprecated: OpenERP saas-3. Use get_working_hours instead."""
|
||||
return self._interval_hours_get(cr, uid, id, dt_from, dt_to, resource_id=resource)
|
||||
|
||||
def _interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource_id=False, timezone_from_uid=None, exclude_leaves=True, context=None):
|
||||
""" Calculates the Total Working hours based on given start_date to
|
||||
end_date, If resource id is supplied that it will consider the source
|
||||
leaves also in calculating the hours.
|
||||
""" Computes working hours between two dates, taking always same hour/minuts.
|
||||
|
||||
@param dt_from : date start to calculate hours
|
||||
@param dt_end : date end to calculate hours
|
||||
@param resource_id: optional resource id, If given resource leave will be
|
||||
considered.
|
||||
@param timezone_from_uid: optional uid, if given we will considerer
|
||||
working hours in that user timezone
|
||||
@param exclude_leaves: optionnal, if set to True (default) we will exclude
|
||||
resource leaves from working hours
|
||||
@param context: current request context
|
||||
@return : Total number of working hours based dt_from and dt_end and
|
||||
resource if supplied.
|
||||
"""
|
||||
utc_tz = pytz.timezone('UTC')
|
||||
local_tz = utc_tz
|
||||
|
||||
if timezone_from_uid:
|
||||
users_obj = self.pool.get('res.users')
|
||||
user_timezone = users_obj.browse(cr, uid, timezone_from_uid, context=context).partner_id.tz
|
||||
if user_timezone:
|
||||
try:
|
||||
local_tz = pytz.timezone(user_timezone)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
pass # fallback to UTC as local timezone
|
||||
|
||||
def utc_to_local_zone(naive_datetime):
|
||||
utc_dt = utc_tz.localize(naive_datetime, is_dst=False)
|
||||
return utc_dt.astimezone(local_tz)
|
||||
|
||||
def float_time_convert(float_val):
|
||||
factor = float_val < 0 and -1 or 1
|
||||
val = abs(float_val)
|
||||
return (factor * int(math.floor(val)), int(round((val % 1) * 60)))
|
||||
|
||||
# Get slots hours per day
|
||||
# {day_of_week: [(8, 12), (13, 17), ...], ...}
|
||||
hours_range_per_weekday = {}
|
||||
if id:
|
||||
cr.execute("select dayofweek, hour_from,hour_to from resource_calendar_attendance where calendar_id=%s order by hour_from", (id,))
|
||||
for weekday, hour_from, hour_to in cr.fetchall():
|
||||
weekday = int(weekday)
|
||||
hours_range_per_weekday.setdefault(weekday, [])
|
||||
hours_range_per_weekday[weekday].append((hour_from, hour_to))
|
||||
else:
|
||||
# considering default working hours (Monday -> Friday, 8 -> 12, 13 -> 17)
|
||||
for weekday in range(5):
|
||||
hours_range_per_weekday[weekday] = [(8, 12), (13, 17)]
|
||||
|
||||
## Interval between dt_from - dt_to
|
||||
##
|
||||
## dt_from dt_to
|
||||
## =============|==================|============
|
||||
## [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]
|
||||
##
|
||||
## [ : start of range
|
||||
## ] : end of range
|
||||
##
|
||||
## case 1: range end before interval start (skip)
|
||||
## case 2: range overlap interval start (fit start to internal)
|
||||
## case 3: range within interval
|
||||
## case 4: range overlap interval end (fit end to interval)
|
||||
## case 5: range start after interval end (skip)
|
||||
|
||||
interval_start = utc_to_local_zone(dt_from)
|
||||
interval_end = utc_to_local_zone(dt_to)
|
||||
hours_timedelta = timedelta()
|
||||
|
||||
# Get leaves for requested resource
|
||||
dt_leaves = set([])
|
||||
if exclude_leaves and id:
|
||||
dt_leaves = set(self._get_leaves(cr, uid, id, resource=resource_id))
|
||||
|
||||
for day in rrule.rrule(rrule.DAILY, dtstart=interval_start,
|
||||
until=interval_end+timedelta(days=1),
|
||||
byweekday=hours_range_per_weekday.keys()):
|
||||
if exclude_leaves and day.strftime('%Y-%m-%d') in dt_leaves:
|
||||
# XXX: futher improve leave management to allow for partial day leave
|
||||
continue
|
||||
for (range_from, range_to) in hours_range_per_weekday.get(day.weekday(), []):
|
||||
range_from_hour, range_from_min = float_time_convert(range_from)
|
||||
range_to_hour, range_to_min = float_time_convert(range_to)
|
||||
daytime_start = local_tz.localize(day.replace(hour=range_from_hour, minute=range_from_min, second=0, tzinfo=None))
|
||||
daytime_end = local_tz.localize(day.replace(hour=range_to_hour, minute=range_to_min, second=0, tzinfo=None))
|
||||
|
||||
# case 1 & 5: time range out of interval
|
||||
if daytime_end < interval_start or daytime_start > interval_end:
|
||||
continue
|
||||
# case 2 & 4: adjust start, end to fit within interval
|
||||
daytime_start = max(daytime_start, interval_start)
|
||||
daytime_end = min(daytime_end, interval_end)
|
||||
|
||||
# case 2+, 4+, 3
|
||||
hours_timedelta += (daytime_end - daytime_start)
|
||||
|
||||
# return timedelta converted to hours
|
||||
return (hours_timedelta.days * 24.0 + hours_timedelta.seconds / 3600.0)
|
||||
:deprecated: OpenERP saas-3. Use get_working_hours instead. Note: since saas-3,
|
||||
now resets hour/minuts. Now counts leave hours instead of all-day leaves."""
|
||||
return self.get_working_hours(
|
||||
cr, uid, id, dt_from, dt_to,
|
||||
compute_leaves=(not exclude_leaves), resource_id=resource_id,
|
||||
default_interval=(8, 16), context=context)
|
||||
|
||||
|
||||
class resource_calendar_attendance(osv.osv):
|
||||
|
@ -374,6 +683,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.
|
||||
|
||||
NOTE: Used in project/project.py
|
||||
"""
|
||||
resource_objs = {}
|
||||
user_pool = self.pool.get('res.users')
|
||||
|
@ -401,6 +712,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
|
||||
|
||||
NOTE: used in project/project.py, and in generate_resources
|
||||
"""
|
||||
resource_calendar_leaves_pool = self.pool.get('resource.calendar.leaves')
|
||||
leave_list = []
|
||||
|
@ -415,10 +728,10 @@ class resource_resource(osv.osv):
|
|||
], context=context)
|
||||
leaves = resource_calendar_leaves_pool.read(cr, uid, leave_ids, ['date_from', 'date_to'], context=context)
|
||||
for i in range(len(leaves)):
|
||||
dt_start = datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S')
|
||||
dt_end = datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S')
|
||||
dt_start = datetime.datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S')
|
||||
dt_end = datetime.datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S')
|
||||
no = dt_end - dt_start
|
||||
[leave_list.append((dt_start + timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
|
||||
[leave_list.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
|
||||
leave_list.sort()
|
||||
return leave_list
|
||||
|
||||
|
@ -426,6 +739,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
|
||||
|
||||
NOTE: used in project/project.py
|
||||
"""
|
||||
if not calendar_id:
|
||||
# Calendar is not specified: working days: 24/7
|
||||
|
|
|
@ -60,12 +60,13 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Working Time" version="7.0">
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="manager"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<field name="attendance_ids"/>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="manager"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<field name="attendance_ids"/>
|
||||
<field name="leave_ids"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -245,13 +246,17 @@
|
|||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_resource_calendar_leave_tree" model="ir.actions.act_window">
|
||||
<field name="name">Resource Leaves</field>
|
||||
<field name="res_model">resource.calendar.leaves</field>
|
||||
<field name="view_mode">tree,form,calendar</field>
|
||||
<field name="search_view_id" ref="view_resource_calendar_leaves_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_resource_config" name="Resource" parent="base.menu_custom" sequence="30"/>
|
||||
<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_resource_calendar" parent="menu_resource_config" sequence="1"/>
|
||||
<menuitem action="resource.action_resource_calendar_leave_tree" id="menu_view_resource_calendar_leaves_search" parent="menu_resource_config" sequence="2"/>
|
||||
<menuitem action="resource.action_resource_resource_tree" id="menu_resource_resource" parent="menu_resource_config" sequence="3"/>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
dt = now - timedelta(days=now.weekday())
|
||||
for resource in resources:
|
||||
result = calendar_pool.working_hours_on_day(cr, uid, resource.calendar_id, dt, context)
|
||||
assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource.'
|
||||
assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource (found %d).' % result
|
||||
-
|
||||
Now, resource "Developer" drafted leave on Thursday in this week.
|
||||
-
|
||||
|
|
|
@ -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:
|
|
@ -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:08:07', '%Y-%m-%d %H:%M:%S') # weekday() returns 1, isoweekday() returns 2
|
||||
self.date2 = datetime.strptime('2013-02-15 10:11:12', '%Y-%m-%d %H:%M:%S') # weekday() returns 4, isoweekday() returns 5
|
||||
# Leave1: 19/02/2013, from 9 to 12, is a day 1
|
||||
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 4
|
||||
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 (day0) -> 01/03/2013 (day4)
|
||||
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 -> 8hours), 4 (8-13, 16-23 -> 12hours)
|
||||
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)
|
|
@ -0,0 +1,445 @@
|
|||
# -*- 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, timedelta
|
||||
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
|
||||
cleaned_intervals = self.resource_calendar.interval_clean(intervals)
|
||||
self.assertEqual(len(cleaned_intervals), 3, 'resource_calendar: wrong interval cleaning')
|
||||
# First interval: 03, unchanged
|
||||
self.assertEqual(cleaned_intervals[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
|
||||
self.assertEqual(cleaned_intervals[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(cleaned_intervals[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
|
||||
self.assertEqual(cleaned_intervals[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(cleaned_intervals[2][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning')
|
||||
self.assertEqual(cleaned_intervals[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')
|
||||
|
||||
# Test: schedule hours on intervals
|
||||
result = self.resource_calendar.interval_schedule_hours(cleaned_intervals, 5.5)
|
||||
self.assertEqual(len(result), 2, 'resource_calendar: wrong hours scheduling in interval')
|
||||
# First interval: 03, 8-10 untouches
|
||||
self.assertEqual(result[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
|
||||
self.assertEqual(result[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
|
||||
# First interval: 04, 08-11:30
|
||||
self.assertEqual(result[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
|
||||
self.assertEqual(result[1][1], datetime.strptime('2013-02-04 11:30:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
|
||||
|
||||
# Test: schedule hours on intervals, backwards
|
||||
cleaned_intervals.reverse()
|
||||
result = self.resource_calendar.interval_schedule_hours(cleaned_intervals, 5.5, remove_at_end=False)
|
||||
self.assertEqual(len(result), 2, 'resource_calendar: wrong hours scheduling in interval')
|
||||
# First interval: 03, 8-10 untouches
|
||||
self.assertEqual(result[0][0], datetime.strptime('2013-02-04 17: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 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
|
||||
# First interval: 04, 08-11:30
|
||||
self.assertEqual(result[1][0], datetime.strptime('2013-02-04 12:30:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval')
|
||||
self.assertEqual(result[1][1], datetime.strptime('2013-02-04 14: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.date())
|
||||
self.assertEqual(date, self.date2.date(), '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.date())
|
||||
self.assertEqual(date, self.date1.date() + 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.date() + relativedelta(days=1))
|
||||
self.assertEqual(date, self.date1.date() + 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.date() + relativedelta(days=-1))
|
||||
self.assertEqual(date, self.date1.date(), '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.date())
|
||||
self.assertEqual(date, self.date2.date() + 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.date())
|
||||
self.assertEqual(date, self.date1.date(), '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.date() + relativedelta(days=1))
|
||||
self.assertEqual(date, self.date2.date(), '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.date() + relativedelta(days=-1))
|
||||
self.assertEqual(date, self.date2.date() + relativedelta(days=-7), 'resource_calendar: wrong previous day computing')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test3: misc
|
||||
# --------------------------------------------------
|
||||
|
||||
weekdays = self.resource_calendar.get_weekdays(cr, uid, self.calendar_id)
|
||||
self.assertEqual(weekdays, [1, 4], 'resource_calendar: wrong weekdays computing')
|
||||
|
||||
attendances = self.resource_calendar.get_attendances_for_weekdays(cr, uid, self.calendar_id, [2, 3, 4, 5])
|
||||
self.assertEqual(set([att.id for att in attendances]), set([self.att2_id, self.att3_id]),
|
||||
'resource_calendar: wrong attendances filtering by weekdays computing')
|
||||
|
||||
def test_20_calendar_working_intervals(self):
|
||||
""" Testing working intervals computing method of resource.calendar """
|
||||
cr, uid = self.cr, self.uid
|
||||
_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# Test: day0 without leaves: 1 interval
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1)
|
||||
self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 09:08:07', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), '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, start_dt=self.date2)
|
||||
self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 10:11:12', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[1][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', _format), '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, start_dt=self.date1.replace(hour=0), 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', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals')
|
||||
|
||||
# Test: day0 with leaves: 2 intervals because of leave between 9 ans 12, ending at 15:45:30
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id,
|
||||
start_dt=self.date1.replace(hour=8) + relativedelta(days=7),
|
||||
end_dt=self.date1.replace(hour=15, minute=45, second=30) + 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:08:07', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong working intervals')
|
||||
self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 15:45:30', _format), 'resource_calendar: wrong working intervals')
|
||||
|
||||
def test_30_calendar_working_days(self):
|
||||
""" Testing calendar hours computation on a working day """
|
||||
cr, uid = self.cr, self.uid
|
||||
_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# Test: day1, beginning at 10:30 -> work from 10:30 (arrival) until 16:00
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0))
|
||||
self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 10:30:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
# Test: hour computation for same interval, should give 5.5
|
||||
wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0))
|
||||
self.assertEqual(wh, 5.5, 'resource_calendar: wrong working interval / day time computing')
|
||||
|
||||
# Test: day1+7 on leave, without leave computation
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(
|
||||
cr, uid, self.calendar_id,
|
||||
start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7)
|
||||
)
|
||||
# Result: day1 (08->16)
|
||||
self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing')
|
||||
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
|
||||
# Test: day1+7 on leave, with generic leave computation
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(
|
||||
cr, uid, self.calendar_id,
|
||||
start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7),
|
||||
compute_leaves=True
|
||||
)
|
||||
# Result: day1 (08->09 + 12->16)
|
||||
self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working interval/day computing')
|
||||
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
|
||||
# Test: day1+14 on leave, with generic leave computation
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(
|
||||
cr, uid, self.calendar_id,
|
||||
start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14),
|
||||
compute_leaves=True
|
||||
)
|
||||
# Result: day1 (08->16)
|
||||
self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing')
|
||||
self.assertEqual(intervals[0][0], datetime.strptime('2013-02-26 08:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
self.assertEqual(intervals[0][1], datetime.strptime('2013-02-26 16:00:00', _format), 'resource_calendar: wrong working interval / day computing')
|
||||
|
||||
# Test: day1+14 on leave, with resource leave computation
|
||||
intervals = self.resource_calendar.get_working_intervals_of_day(
|
||||
cr, uid, self.calendar_id,
|
||||
start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14),
|
||||
compute_leaves=True,
|
||||
resource_id=self.resource1_id
|
||||
)
|
||||
# Result: nothing, because on leave
|
||||
self.assertEqual(len(intervals), 0, 'resource_calendar: wrong working interval/day computing')
|
||||
|
||||
def test_40_calendar_hours_scheduling(self):
|
||||
""" Testing calendar hours scheduling """
|
||||
cr, uid = self.cr, self.uid
|
||||
_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test0: schedule hours backwards (old interval_min_get)
|
||||
# Done without calendar
|
||||
# --------------------------------------------------
|
||||
|
||||
# Done without calendar
|
||||
# res = self.resource_calendar.interval_min_get(cr, uid, None, self.date1, 40, resource=False)
|
||||
# res: (datetime.datetime(2013, 2, 7, 9, 8, 7), datetime.datetime(2013, 2, 12, 9, 8, 7))
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test1: schedule hours backwards (old interval_min_get)
|
||||
# --------------------------------------------------
|
||||
|
||||
# res = self.resource_calendar.interval_min_get(cr, uid, self.calendar_id, self.date1, 40, resource=False)
|
||||
# (datetime.datetime(2013, 1, 29, 9, 0), datetime.datetime(2013, 1, 29, 16, 0))
|
||||
# (datetime.datetime(2013, 2, 1, 8, 0), datetime.datetime(2013, 2, 1, 13, 0))
|
||||
# (datetime.datetime(2013, 2, 1, 16, 0), datetime.datetime(2013, 2, 1, 23, 0))
|
||||
# (datetime.datetime(2013, 2, 5, 8, 0), datetime.datetime(2013, 2, 5, 16, 0))
|
||||
# (datetime.datetime(2013, 2, 8, 8, 0), datetime.datetime(2013, 2, 8, 13, 0))
|
||||
# (datetime.datetime(2013, 2, 8, 16, 0), datetime.datetime(2013, 2, 8, 23, 0))
|
||||
# (datetime.datetime(2013, 2, 12, 8, 0), datetime.datetime(2013, 2, 12, 9, 0))
|
||||
|
||||
res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, -40, day_dt=self.date1.replace(minute=0, second=0))
|
||||
# current day, limited at 09:00 because of day_dt specified -> 1 hour
|
||||
self.assertEqual(res[-1][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-1][1], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
# previous days: 5+7 hours / 8 hours / 5+7 hours -> 32 hours
|
||||
self.assertEqual(res[-2][0], datetime.strptime('2013-02-08 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-2][1], datetime.strptime('2013-02-08 23:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-3][0], datetime.strptime('2013-02-08 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-3][1], datetime.strptime('2013-02-08 13:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-4][0], datetime.strptime('2013-02-05 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-4][1], datetime.strptime('2013-02-05 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-5][0], datetime.strptime('2013-02-01 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-5][1], datetime.strptime('2013-02-01 23:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-6][0], datetime.strptime('2013-02-01 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-6][1], datetime.strptime('2013-02-01 13:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
# 7 hours remaining
|
||||
self.assertEqual(res[-7][0], datetime.strptime('2013-01-29 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[-7][1], datetime.strptime('2013-01-29 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
# Compute scheduled hours
|
||||
td = timedelta()
|
||||
for item in res:
|
||||
td += item[1] - item[0]
|
||||
self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test2: schedule hours forward (old interval_get)
|
||||
# --------------------------------------------------
|
||||
|
||||
# res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True)
|
||||
# (datetime.datetime(2013, 2, 12, 9, 0), datetime.datetime(2013, 2, 12, 16, 0))
|
||||
# (datetime.datetime(2013, 2, 15, 8, 0), datetime.datetime(2013, 2, 15, 13, 0))
|
||||
# (datetime.datetime(2013, 2, 15, 16, 0), datetime.datetime(2013, 2, 15, 23, 0))
|
||||
# (datetime.datetime(2013, 2, 22, 8, 0), datetime.datetime(2013, 2, 22, 13, 0))
|
||||
# (datetime.datetime(2013, 2, 22, 16, 0), datetime.datetime(2013, 2, 22, 23, 0))
|
||||
# (datetime.datetime(2013, 2, 26, 8, 0), datetime.datetime(2013, 2, 26, 16, 0))
|
||||
# (datetime.datetime(2013, 3, 1, 8, 0), datetime.datetime(2013, 3, 1, 9, 0))
|
||||
|
||||
res = self.resource_calendar.schedule_hours(
|
||||
cr, uid, self.calendar_id, 40,
|
||||
day_dt=self.date1.replace(minute=0, second=0)
|
||||
)
|
||||
self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[1][0], datetime.strptime('2013-02-15 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[1][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[2][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[2][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[3][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[3][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[4][0], datetime.strptime('2013-02-22 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[4][1], datetime.strptime('2013-02-22 13:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[5][0], datetime.strptime('2013-02-22 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[5][1], datetime.strptime('2013-02-22 23:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[6][0], datetime.strptime('2013-02-26 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[6][1], datetime.strptime('2013-02-26 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
td = timedelta()
|
||||
for item in res:
|
||||
td += item[1] - item[0]
|
||||
self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling')
|
||||
|
||||
# res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=self.resource1_id, byday=True)
|
||||
# (datetime.datetime(2013, 2, 12, 9, 0), datetime.datetime(2013, 2, 12, 16, 0))
|
||||
# (datetime.datetime(2013, 2, 15, 8, 0), datetime.datetime(2013, 2, 15, 13, 0))
|
||||
# (datetime.datetime(2013, 2, 15, 16, 0), datetime.datetime(2013, 2, 15, 23, 0))
|
||||
# (datetime.datetime(2013, 3, 1, 8, 0), datetime.datetime(2013, 3, 1, 13, 0))
|
||||
# (datetime.datetime(2013, 3, 1, 16, 0), datetime.datetime(2013, 3, 1, 23, 0))
|
||||
# (datetime.datetime(2013, 3, 5, 8, 0), datetime.datetime(2013, 3, 5, 16, 0))
|
||||
# (datetime.datetime(2013, 3, 8, 8, 0), datetime.datetime(2013, 3, 8, 9, 0))
|
||||
|
||||
res = self.resource_calendar.schedule_hours(
|
||||
cr, uid, self.calendar_id, 40,
|
||||
day_dt=self.date1.replace(minute=0, second=0),
|
||||
compute_leaves=True,
|
||||
resource_id=self.resource1_id
|
||||
)
|
||||
self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[1][0], datetime.strptime('2013-02-15 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[1][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[2][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[2][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[3][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[3][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[4][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[4][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[5][0], datetime.strptime('2013-02-22 08:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[5][1], datetime.strptime('2013-02-22 09:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[6][0], datetime.strptime('2013-02-22 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[6][1], datetime.strptime('2013-02-22 23:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[7][0], datetime.strptime('2013-03-01 11:30:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[7][1], datetime.strptime('2013-03-01 13:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[8][0], datetime.strptime('2013-03-01 16:00:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
self.assertEqual(res[8][1], datetime.strptime('2013-03-01 22:30:00', _format), 'resource_calendar: wrong hours scheduling')
|
||||
td = timedelta()
|
||||
for item in res:
|
||||
td += item[1] - item[0]
|
||||
self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test3: working hours (old _interval_hours_get)
|
||||
# --------------------------------------------------
|
||||
|
||||
# old API: resource without leaves
|
||||
# res: 2 weeks -> 40 hours
|
||||
res = self.resource_calendar._interval_hours_get(
|
||||
cr, uid, self.calendar_id,
|
||||
self.date1.replace(hour=6, minute=0),
|
||||
self.date2.replace(hour=23, minute=0) + relativedelta(days=7),
|
||||
resource_id=self.resource1_id, exclude_leaves=True)
|
||||
self.assertEqual(res, 40.0, 'resource_calendar: wrong _interval_hours_get compatibility computation')
|
||||
|
||||
# new API: resource without leaves
|
||||
# res: 2 weeks -> 40 hours
|
||||
res = self.resource_calendar.get_working_hours(
|
||||
cr, uid, self.calendar_id,
|
||||
self.date1.replace(hour=6, minute=0),
|
||||
self.date2.replace(hour=23, minute=0) + relativedelta(days=7),
|
||||
compute_leaves=False, resource_id=self.resource1_id)
|
||||
self.assertEqual(res, 40.0, 'resource_calendar: wrong get_working_hours computation')
|
||||
|
||||
# old API: resource and leaves
|
||||
# res: 2 weeks -> 40 hours - (3+4) leave hours
|
||||
res = self.resource_calendar._interval_hours_get(
|
||||
cr, uid, self.calendar_id,
|
||||
self.date1.replace(hour=6, minute=0),
|
||||
self.date2.replace(hour=23, minute=0) + relativedelta(days=7),
|
||||
resource_id=self.resource1_id, exclude_leaves=False)
|
||||
self.assertEqual(res, 33.0, 'resource_calendar: wrong _interval_hours_get compatibility computation')
|
||||
|
||||
# new API: resource and leaves
|
||||
# res: 2 weeks -> 40 hours - (3+4) leave hours
|
||||
res = self.resource_calendar.get_working_hours(
|
||||
cr, uid, self.calendar_id,
|
||||
self.date1.replace(hour=6, minute=0),
|
||||
self.date2.replace(hour=23, minute=0) + relativedelta(days=7),
|
||||
compute_leaves=True, resource_id=self.resource1_id)
|
||||
self.assertEqual(res, 33.0, 'resource_calendar: wrong get_working_hours computation')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test4: misc
|
||||
# --------------------------------------------------
|
||||
|
||||
# Test without calendar and default_interval
|
||||
res = self.resource_calendar.get_working_hours(
|
||||
cr, uid, None,
|
||||
self.date1.replace(hour=6, minute=0),
|
||||
self.date2.replace(hour=23, minute=0),
|
||||
compute_leaves=True, resource_id=self.resource1_id,
|
||||
default_interval=(8, 16))
|
||||
self.assertEqual(res, 32.0, 'resource_calendar: wrong get_working_hours computation')
|
||||
|
||||
def test_50_calendar_schedule_days(self):
|
||||
""" Testing calendar days scheduling """
|
||||
cr, uid = self.cr, self.uid
|
||||
_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test1: with calendar
|
||||
# --------------------------------------------------
|
||||
|
||||
res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1)
|
||||
self.assertEqual(res.date(), datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling')
|
||||
|
||||
res = self.resource_calendar.schedule_days_get_date(
|
||||
cr, uid, self.calendar_id, 5, day_date=self.date1,
|
||||
compute_leaves=True, resource_id=self.resource1_id)
|
||||
self.assertEqual(res.date(), datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test2: misc
|
||||
# --------------------------------------------------
|
||||
|
||||
# Without calendar, should only count days -> 12 -> 16, 5 days with default intervals
|
||||
res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1, default_interval=(8, 16))
|
||||
self.assertEqual(res, datetime.strptime('2013-02-16 16:00:00', _format), 'resource_calendar: wrong days scheduling')
|
Loading…
Reference in New Issue