[FIX] Renamed faces and removed print statements in it
bzr revid: rvo@tinyerp.co.in-20100129074800-cjtob9irn861g2ff
This commit is contained in:
parent
404bf5ca1a
commit
557de24693
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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 pcalendar import Calendar, WorkingDate, StartDate, EndDate, Minutes
|
||||
|
||||
from task import Project, BalancedProject, AdjustedProject, Task, \
|
||||
STRICT, SLOPPY, SMART, Multi, YearlyMax, WeeklyMax, MonthlyMax, \
|
||||
DailyMax, VariableLoad
|
||||
|
||||
from resource import Resource
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,79 @@
|
|||
#@+leo-ver=4
|
||||
#@+node:@file observer.py
|
||||
#@@language python
|
||||
#@<< Copyright >>
|
||||
#@+node:<< Copyright >>
|
||||
############################################################################
|
||||
# Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
|
||||
# mreithinger@web.de
|
||||
#
|
||||
# This file is part of faces.
|
||||
#
|
||||
# faces is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# faces 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
############################################################################
|
||||
|
||||
#@-node:<< Copyright >>
|
||||
#@nl
|
||||
"""
|
||||
This module contains the base class for all observer objects
|
||||
"""
|
||||
#@<< Imports >>
|
||||
#@+node:<< Imports >>
|
||||
#@-node:<< Imports >>
|
||||
#@nl
|
||||
_is_source_ = True
|
||||
#@+others
|
||||
#@+node:class Observer
|
||||
class Observer(object):
|
||||
"""
|
||||
Base Class for all charts and reports.
|
||||
|
||||
@var visible: Specifies if the observer is visible
|
||||
at the navigation bar inside the gui.
|
||||
|
||||
@var link_view: syncronizes the marked objects in all views.
|
||||
|
||||
"""
|
||||
#@ << declarations >>
|
||||
#@+node:<< declarations >>
|
||||
__type_name__ = None
|
||||
__type_image__ = None
|
||||
visible = True
|
||||
link_view = True
|
||||
|
||||
__attrib_completions__ = { "visible" : 'visible = False',
|
||||
"link_view" : "link_view = False" }
|
||||
|
||||
|
||||
#@-node:<< declarations >>
|
||||
#@nl
|
||||
|
||||
#@ @+others
|
||||
#@+node:register_editors
|
||||
def register_editors(cls, registry):
|
||||
pass
|
||||
|
||||
register_editors = classmethod(register_editors)
|
||||
|
||||
#@-node:register_editors
|
||||
#@-others
|
||||
|
||||
#@-node:class Observer
|
||||
#@-others
|
||||
factories = { }
|
||||
clear_cache_funcs = {}
|
||||
#@-node:@file observer.py
|
||||
#@-leo
|
|
@ -0,0 +1,960 @@
|
|||
#@+leo-ver=4
|
||||
#@+node:@file pcalendar.py
|
||||
#@@language python
|
||||
#@<< Copyright >>
|
||||
#@+node:<< Copyright >>
|
||||
############################################################################
|
||||
# Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
|
||||
# mreithinger@web.de
|
||||
#
|
||||
# This file is part of faces.
|
||||
#
|
||||
# faces is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# faces 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
############################################################################
|
||||
|
||||
#@-node:<< Copyright >>
|
||||
#@nl
|
||||
"""
|
||||
This module contains all classes and functions for the project plan calendar
|
||||
"""
|
||||
#@<< Imports >>
|
||||
#@+node:<< Imports >>
|
||||
from string import *
|
||||
import datetime
|
||||
import time
|
||||
import re
|
||||
import locale
|
||||
import bisect
|
||||
import sys
|
||||
|
||||
TIME_RANGE_PATTERN = re.compile("(\\d+):(\\d+)\\s*-\\s*(\\d+):(\\d+)")
|
||||
TIME_DELTA_PATTERN = re.compile("([-+]?\\d+(\\.\\d+)?)([dwmyMH])")
|
||||
|
||||
DEFAULT_MINIMUM_TIME_UNIT = 15
|
||||
DEFAULT_WORKING_DAYS_PER_WEEK = 5
|
||||
DEFAULT_WORKING_DAYS_PER_MONTH = 20
|
||||
DEFAULT_WORKING_DAYS_PER_YEAR = 200
|
||||
DEFAULT_WORKING_HOURS_PER_DAY = 8
|
||||
|
||||
DEFAULT_WORKING_TIMES = ( (8 * 60, 12 * 60 ),
|
||||
(13 * 60, 17 * 60 ) )
|
||||
DEFAULT_WORKING_DAYS = { 0 : DEFAULT_WORKING_TIMES,
|
||||
1 : DEFAULT_WORKING_TIMES,
|
||||
2 : DEFAULT_WORKING_TIMES,
|
||||
3 : DEFAULT_WORKING_TIMES,
|
||||
4 : DEFAULT_WORKING_TIMES,
|
||||
5 : (),
|
||||
6 : () }
|
||||
|
||||
#@-node:<< Imports >>
|
||||
#@nl
|
||||
#@+others
|
||||
#@+node:to_time_range
|
||||
def to_time_range(src):
|
||||
"""
|
||||
converts a string to a timerange, i.e
|
||||
(from, to)
|
||||
from, to are ints, specifing the minutes since midnight
|
||||
"""
|
||||
|
||||
if not src: return ()
|
||||
|
||||
mo = TIME_RANGE_PATTERN.match(src)
|
||||
if not mo:
|
||||
raise ValueError("%s is no time range" % src)
|
||||
|
||||
from_time = int(mo.group(1)) * 60 + int(mo.group(2))
|
||||
to_time = int(mo.group(3)) * 60 + int(mo.group(4))
|
||||
return from_time, to_time
|
||||
#@-node:to_time_range
|
||||
#@+node:to_datetime
|
||||
def to_datetime(src):
|
||||
"""
|
||||
a tolerant conversion function to convert different strings
|
||||
to a datetime.dateime
|
||||
"""
|
||||
|
||||
#to get the original value for wrappers
|
||||
new = getattr(src, "_value", src)
|
||||
while new is not src:
|
||||
src = new
|
||||
new = getattr(src, "_value", src)
|
||||
|
||||
if isinstance(src, _WorkingDateBase):
|
||||
src = src.to_datetime()
|
||||
|
||||
if isinstance(src, datetime.datetime):
|
||||
return src
|
||||
|
||||
src = str(src)
|
||||
|
||||
formats = [ "%x %H:%M",
|
||||
"%x",
|
||||
"%Y-%m-%d %H:%M",
|
||||
"%y-%m-%d %H:%M",
|
||||
"%d.%m.%Y %H:%M",
|
||||
"%d.%m.%y %H:%M",
|
||||
"%Y%m%d %H:%M",
|
||||
"%d/%m/%y %H:%M",
|
||||
"%d/%m/%Y %H:%M",
|
||||
"%d/%m/%Y",
|
||||
"%d/%m/%y",
|
||||
"%Y-%m-%d",
|
||||
"%y-%m-%d",
|
||||
"%d.%m.%Y",
|
||||
"%d.%m.%y",
|
||||
"%Y%m%d" ]
|
||||
for f in formats:
|
||||
try:
|
||||
conv = time.strptime(src, f)
|
||||
|
||||
return datetime.datetime(*conv[0:-3])
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
raise TypeError("'%s' (%s) is not a datetime" % (src, str(type(src))))
|
||||
#@-node:
|
||||
#@+node:_to_days
|
||||
def _to_days(src):
|
||||
"""
|
||||
converts a string of the day abreviations mon, tue, wed,
|
||||
thu, fri, sat, sun to a dir with correct weekday indices.
|
||||
For Example
|
||||
convert_to_days('mon, tue, thu') results in
|
||||
{ 0:1, 1:1, 3:1 }
|
||||
"""
|
||||
|
||||
tokens = src.split(",")
|
||||
result = { }
|
||||
for t in tokens:
|
||||
try:
|
||||
index = { "mon" : 0,
|
||||
"tue" : 1,
|
||||
"wed" : 2,
|
||||
"thu" : 3,
|
||||
"fri" : 4,
|
||||
"sat" : 5,
|
||||
"sun" : 6 } [ lower(t.strip()) ]
|
||||
result[index] = 1
|
||||
except:
|
||||
raise ValueError("%s is not a day" % (t))
|
||||
|
||||
return result
|
||||
#@-node:_to_days
|
||||
#@+node:_add_to_time_spans
|
||||
def _add_to_time_spans(src, to_add, is_free):
|
||||
if not isinstance(to_add, (tuple, list)):
|
||||
to_add = (to_add,)
|
||||
|
||||
tmp = []
|
||||
for start, end, f in src:
|
||||
tmp.append((start, True, f))
|
||||
tmp.append((end, False, f))
|
||||
|
||||
for v in to_add:
|
||||
if isinstance(v, (tuple, list)):
|
||||
start = to_datetime(v[0])
|
||||
end = to_datetime(v[1])
|
||||
else:
|
||||
start = to_datetime(v)
|
||||
end = start.replace(hour=0, minute=0) + datetime.timedelta(1)
|
||||
|
||||
tmp.append((start, start <= end, is_free))
|
||||
tmp.append((end, start > end, is_free))
|
||||
|
||||
tmp.sort()
|
||||
|
||||
# 0: date
|
||||
# 1: is_start
|
||||
# 2: is_free
|
||||
sequence = []
|
||||
free_count = 0
|
||||
work_count = 0
|
||||
last = None
|
||||
for date, is_start, is_free in tmp:
|
||||
if is_start:
|
||||
if is_free:
|
||||
if not free_count and not work_count:
|
||||
last = date
|
||||
|
||||
free_count += 1
|
||||
else:
|
||||
if not work_count:
|
||||
if free_count: sequence.append((last, date, True))
|
||||
last = date
|
||||
work_count += 1
|
||||
else:
|
||||
if is_free:
|
||||
assert(free_count > 0)
|
||||
free_count -= 1
|
||||
if not free_count and not work_count:
|
||||
sequence.append((last, date, True))
|
||||
else:
|
||||
assert(work_count > 0)
|
||||
work_count -= 1
|
||||
if not work_count: sequence.append((last, date, False))
|
||||
if free_count: last = date
|
||||
|
||||
return tuple(sequence)
|
||||
#@-node:_add_to_time_spans
|
||||
#@+node:to_timedelta
|
||||
def to_timedelta(src, cal=None, is_duration=False):
|
||||
"""
|
||||
converts a string to a datetime.timedelta. If cal is specified
|
||||
it will be used for getting the working times. if is_duration=True
|
||||
working times will not be considered. Valid units are
|
||||
d for Days
|
||||
w for Weeks
|
||||
m for Months
|
||||
y for Years
|
||||
H for Hours
|
||||
M for Minutes
|
||||
"""
|
||||
|
||||
cal = cal or _default_calendar
|
||||
if isinstance(src, datetime.timedelta):
|
||||
return datetime.timedelta(src.days, seconds=src.seconds, calendar=cal)
|
||||
|
||||
if isinstance(src, (long, int, float)):
|
||||
src = "%sM" % str(src)
|
||||
|
||||
if not isinstance(src, basestring):
|
||||
raise ValueError("%s is not a duration" % (repr(src)))
|
||||
|
||||
src = src.strip()
|
||||
|
||||
if is_duration:
|
||||
d_p_w = 7
|
||||
d_p_m = 30
|
||||
d_p_y = 360
|
||||
d_w_h = 24
|
||||
else:
|
||||
d_p_w = cal.working_days_per_week
|
||||
d_p_m = cal.working_days_per_month
|
||||
d_p_y = cal.working_days_per_year
|
||||
d_w_h = cal.working_hours_per_day
|
||||
|
||||
def convert_minutes(minutes):
|
||||
minutes = int(minutes)
|
||||
hours = minutes / 60
|
||||
minutes = minutes % 60
|
||||
days = hours / d_w_h
|
||||
hours = hours % d_w_h
|
||||
return [ days, 0, 0, 0, minutes, hours ]
|
||||
|
||||
def convert_days(value):
|
||||
days = int(value)
|
||||
value -= days
|
||||
value *= d_w_h
|
||||
hours = int(value)
|
||||
value -= hours
|
||||
value *= 60
|
||||
minutes = round(value)
|
||||
return [ days, 0, 0, 0, minutes, hours ]
|
||||
|
||||
sum_args = [ 0, 0, 0, 0, 0, 0 ]
|
||||
|
||||
split = src.split(" ")
|
||||
for s in split:
|
||||
mo = TIME_DELTA_PATTERN.match(s)
|
||||
if not mo:
|
||||
raise ValueError(src +
|
||||
" is not a valid duration: valid"
|
||||
" units are: d w m y M H")
|
||||
|
||||
unit = mo.group(3)
|
||||
val = float(mo.group(1))
|
||||
|
||||
if unit == 'd':
|
||||
args = convert_days(val)
|
||||
elif unit == 'w':
|
||||
args = convert_days(val * d_p_w)
|
||||
elif unit == 'm':
|
||||
args = convert_days(val * d_p_m)
|
||||
elif unit == 'y':
|
||||
args = convert_days(val * d_p_y)
|
||||
elif unit == 'M':
|
||||
args = convert_minutes(val)
|
||||
elif unit == 'H':
|
||||
args = convert_minutes(val * 60)
|
||||
|
||||
sum_args = [ a + b for a, b in zip(sum_args, args) ]
|
||||
|
||||
sum_args = tuple(sum_args)
|
||||
return datetime.timedelta(*sum_args)
|
||||
#@-node:to_timedelta
|
||||
#@+node:timedelta_to_str
|
||||
def timedelta_to_str(delta, format, cal=None, is_duration=False):
|
||||
cal = cal or _default_calendar
|
||||
if is_duration:
|
||||
d_p_w = 7
|
||||
d_p_m = 30
|
||||
d_p_y = 365
|
||||
d_w_h = 24
|
||||
else:
|
||||
d_p_w = cal.working_days_per_week
|
||||
d_p_m = cal.working_days_per_month
|
||||
d_p_y = cal.working_days_per_year
|
||||
d_w_h = cal.working_hours_per_day
|
||||
|
||||
has_years = format.find("%y") > -1
|
||||
has_minutes = format.find("%M") > -1
|
||||
has_hours = format.find("%H") > -1 or has_minutes
|
||||
has_days = format.find("%d") > -1
|
||||
has_weeks = format.find("%w") > -1
|
||||
has_months = format.find("%m") > -1
|
||||
|
||||
result = format
|
||||
days = delta.days
|
||||
|
||||
d_r = (days, format)
|
||||
minutes = delta.seconds / 60
|
||||
|
||||
def rebase(d_r, cond1, cond2, letter, divisor):
|
||||
#rebase the days
|
||||
if not cond1: return d_r
|
||||
|
||||
days, result = d_r
|
||||
|
||||
if cond2:
|
||||
val = days / divisor
|
||||
if not val:
|
||||
result = re.sub("{[^{]*?%" + letter + "[^}]*?}", "", result)
|
||||
|
||||
result = result.replace("%" + letter, str(val))
|
||||
days %= divisor
|
||||
else:
|
||||
result = result.replace("%" + letter,
|
||||
locale.format("%.2f",
|
||||
(float(days) / divisor)))
|
||||
|
||||
return (days, result)
|
||||
|
||||
d_r = rebase(d_r, has_years, has_months or has_weeks or has_days, "y", d_p_y)
|
||||
d_r = rebase(d_r, has_months, has_weeks or has_days, "m", d_p_m)
|
||||
d_r = rebase(d_r, has_weeks, has_days, "w", d_p_w)
|
||||
days, result = d_r
|
||||
|
||||
if not has_days:
|
||||
minutes += days * d_w_h * 60
|
||||
days = 0
|
||||
|
||||
if has_hours:
|
||||
if not days:
|
||||
result = re.sub("{[^{]*?%d[^}]*?}", "", result)
|
||||
|
||||
result = result.replace("%d", str(days))
|
||||
else:
|
||||
result = result.replace("%d",
|
||||
"%.2f" % (days + float(minutes)
|
||||
/ (d_w_h * 60)))
|
||||
|
||||
if has_hours:
|
||||
if has_minutes:
|
||||
val = minutes / 60
|
||||
if not val:
|
||||
result = re.sub("{[^{]*?%H[^}]*?}", "", result)
|
||||
|
||||
result = result.replace("%H", str(val))
|
||||
minutes %= 60
|
||||
else:
|
||||
result = result.replace("%H", "%.2f" % (float(minutes) / 60))
|
||||
|
||||
if not minutes:
|
||||
result = re.sub("{[^{]*?%M[^}]*?}", "", result)
|
||||
|
||||
result = result.replace("%M", str(minutes))
|
||||
|
||||
result = result.replace("{", "")
|
||||
result = result.replace("}", "")
|
||||
return result.strip()
|
||||
#@-node:timedelta_to_str
|
||||
#@+node:strftime
|
||||
def strftime(dt, format):
|
||||
"""
|
||||
an extended version of strftime, that introduces some new
|
||||
directives:
|
||||
%IW iso week number
|
||||
%IY iso year
|
||||
%IB full month name appropriate to iso week
|
||||
%ib abbreviated month name appropriate to iso week
|
||||
%im month as decimal number appropriate to iso week
|
||||
"""
|
||||
iso = dt.isocalendar()
|
||||
if iso[0] != dt.year:
|
||||
iso_date = dt.replace(day=1, month=1)
|
||||
format = format \
|
||||
.replace("%IB", iso_date.strftime("%B"))\
|
||||
.replace("%ib", iso_date.strftime("%b"))\
|
||||
.replace("%im", iso_date.strftime("%m"))
|
||||
else:
|
||||
format = format \
|
||||
.replace("%IB", "%B")\
|
||||
.replace("%ib", "%b")\
|
||||
.replace("%im", "%m")
|
||||
|
||||
format = format \
|
||||
.replace("%IW", str(iso[1]))\
|
||||
.replace("%IY", str(iso[0]))\
|
||||
|
||||
return dt.strftime(format)
|
||||
#@-node:strftime
|
||||
#@+node:union
|
||||
def union(*calendars):
|
||||
"""
|
||||
returns a calendar that unifies all working times
|
||||
"""
|
||||
#@ << check arguments >>
|
||||
#@+node:<< check arguments >>
|
||||
if len(calendars) == 1:
|
||||
calendars = calendars[0]
|
||||
#@nonl
|
||||
#@-node:<< check arguments >>
|
||||
#@nl
|
||||
#@ << intersect vacations >>
|
||||
#@+node:<< intersect vacations >>
|
||||
free_time = []
|
||||
for c in calendars:
|
||||
for start, end, is_free in c.time_spans:
|
||||
if is_free:
|
||||
free_time.append((start, False))
|
||||
free_time.append((end, True))
|
||||
|
||||
count = len(calendars)
|
||||
open = 0
|
||||
time_spans = []
|
||||
free_time.sort()
|
||||
for date, is_end in free_time:
|
||||
if is_end:
|
||||
if open == count:
|
||||
time_spans.append((start, date, True))
|
||||
open -= 1
|
||||
else:
|
||||
open += 1
|
||||
start = date
|
||||
#@-node:<< intersect vacations >>
|
||||
#@nl
|
||||
#@ << unify extra worktime >>
|
||||
#@+node:<< unify extra worktime >>
|
||||
for c in calendars:
|
||||
for start, end, is_free in c.time_spans:
|
||||
if not is_free:
|
||||
time_spans = _add_to_time_spans(time_spans, start, end)
|
||||
#@nonl
|
||||
#@-node:<< unify extra worktime >>
|
||||
#@nl
|
||||
#@ << unify working times >>
|
||||
#@+node:<< unify working times >>
|
||||
working_times = {}
|
||||
for d in range(0, 7):
|
||||
times = []
|
||||
for c in calendars:
|
||||
for start, end in c.working_times.get(d, []):
|
||||
times.append((start, False))
|
||||
times.append((end, True))
|
||||
|
||||
times.sort()
|
||||
open = 0
|
||||
ti = []
|
||||
start = None
|
||||
for time, is_end in times:
|
||||
if not is_end:
|
||||
if not start: start = time
|
||||
open += 1
|
||||
else:
|
||||
open -= 1
|
||||
if not open:
|
||||
ti.append((start, time))
|
||||
start = None
|
||||
|
||||
if ti:
|
||||
working_times[d] = ti
|
||||
#@-node:<< unify working times >>
|
||||
#@nl
|
||||
#@ << create result calendar >>
|
||||
#@+node:<< create result calendar >>
|
||||
result = Calendar()
|
||||
result.working_times = working_times
|
||||
result.time_spans = time_spans
|
||||
result._recalc_working_time()
|
||||
result._build_mapping()
|
||||
#@nonl
|
||||
#@-node:<< create result calendar >>
|
||||
#@nl
|
||||
return result
|
||||
#@nonl
|
||||
#@-node:union
|
||||
#@+node:class _CalendarItem
|
||||
class _CalendarItem(int):
|
||||
#@ << class _CalendarItem declarations >>
|
||||
#@+node:<< class _CalendarItem declarations >>
|
||||
__slots__ = ()
|
||||
calender = None
|
||||
|
||||
|
||||
#@-node:<< class _CalendarItem declarations >>
|
||||
#@nl
|
||||
#@ @+others
|
||||
#@+node:__new__
|
||||
def __new__(cls, val):
|
||||
try:
|
||||
return int.__new__(cls, val)
|
||||
except OverflowError:
|
||||
return int.__new__(cls, sys.maxint)
|
||||
#@-node:__new__
|
||||
#@+node:round
|
||||
def round(self, round_up=True):
|
||||
m_t_u = self.calendar.minimum_time_unit
|
||||
|
||||
minutes = int(self)
|
||||
base = (minutes / m_t_u) * m_t_u
|
||||
minutes %= m_t_u
|
||||
|
||||
round_up = round_up and minutes > 0 or minutes > m_t_u / 2
|
||||
if round_up: base += m_t_u
|
||||
return self.__class__(base)
|
||||
#@-node:round
|
||||
#@-others
|
||||
#@-node:class _CalendarItem
|
||||
#@+node:class _Minutes
|
||||
class _Minutes(_CalendarItem):
|
||||
#@ << class _Minutes declarations >>
|
||||
#@+node:<< class _Minutes declarations >>
|
||||
__slots__ = ()
|
||||
STR_FORMAT = "{%dd}{ %HH}{ %MM}"
|
||||
|
||||
|
||||
#@-node:<< class _Minutes declarations >>
|
||||
#@nl
|
||||
#@ @+others
|
||||
#@+node:__new__
|
||||
def __new__(cls, src=0, is_duration=False):
|
||||
"""
|
||||
converts a timedelta in working minutes.
|
||||
"""
|
||||
if isinstance(src, cls) or type(src) is int:
|
||||
return _CalendarItem.__new__(cls, src)
|
||||
|
||||
cal = cls.calendar
|
||||
if not isinstance(src, datetime.timedelta):
|
||||
src = to_timedelta(src, cal, is_duration)
|
||||
|
||||
d_w_h = is_duration and 24 or cal.working_hours_per_day
|
||||
src = src.days * d_w_h * 60 + src.seconds / 60
|
||||
return _CalendarItem.__new__(cls, src)
|
||||
#@-node:__new__
|
||||
#@+node:__cmp__
|
||||
def __cmp__(self, other):
|
||||
return cmp(int(self), int(self.__class__(other)))
|
||||
#@-node:__cmp__
|
||||
#@+node:__add__
|
||||
def __add__(self, other):
|
||||
try:
|
||||
return self.__class__(int(self) + int(self.__class__(other)))
|
||||
except:
|
||||
return NotImplemented
|
||||
#@-node:__add__
|
||||
#@+node:__sub__
|
||||
def __sub__(self, other):
|
||||
try:
|
||||
return self.__class__(int(self) - int(self.__class__(other)))
|
||||
except:
|
||||
return NotImplemented
|
||||
#@-node:__sub__
|
||||
#@+node:to_timedelta
|
||||
def to_timedelta(self, is_duration=False):
|
||||
d_w_h = is_duration and 24 or self.calendar.working_hours_per_day
|
||||
minutes = int(self)
|
||||
hours = minutes / 60
|
||||
minutes = minutes % 60
|
||||
days = hours / d_w_h
|
||||
hours = hours % d_w_h
|
||||
return datetime.timedelta(days, hours=hours, minutes=minutes)
|
||||
#@nonl
|
||||
#@-node:to_timedelta
|
||||
#@+node:strftime
|
||||
def strftime(self, format=None, is_duration=False):
|
||||
td = self.to_timedelta(is_duration)
|
||||
return timedelta_to_str(td, format or self.STR_FORMAT,
|
||||
self.calendar, is_duration)
|
||||
#@nonl
|
||||
#@-node:strftime
|
||||
#@-others
|
||||
#@-node:class _Minutes
|
||||
#@+node:class _WorkingDateBase
|
||||
class _WorkingDateBase(_CalendarItem):
|
||||
"""
|
||||
A daytetime which has only valid values within the
|
||||
workingtimes of a specific calendar
|
||||
"""
|
||||
#@ << class _WorkingDateBase declarations >>
|
||||
#@+node:<< class _WorkingDateBase declarations >>
|
||||
timetuple = True
|
||||
STR_FORMAT = "%x %H:%M"
|
||||
_minutes = _Minutes
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
#@-node:<< class _WorkingDateBase declarations >>
|
||||
#@nl
|
||||
#@ @+others
|
||||
#@+node:__new__
|
||||
def __new__(cls, src):
|
||||
#cls.__bases__[0] is the base of
|
||||
#the calendar specific StartDate and EndDate
|
||||
|
||||
if isinstance(src, cls.__bases__[0]) or type(src) in (int, float):
|
||||
return _CalendarItem.__new__(cls, src)
|
||||
|
||||
|
||||
src = cls.calendar.from_datetime(to_datetime(src))
|
||||
return _CalendarItem.__new__(cls, src)
|
||||
#@-node:__new__
|
||||
#@+node:__repr__
|
||||
def __repr__(self):
|
||||
return self.strftime()
|
||||
#@-node:__repr__
|
||||
#@+node:to_datetime
|
||||
def to_datetime(self):
|
||||
return self.to_starttime()
|
||||
#@-node:to_datetime
|
||||
#@+node:to_starttime
|
||||
def to_starttime(self):
|
||||
return self.calendar.to_starttime(self)
|
||||
#@-node:to_starttime
|
||||
#@+node:to_endtime
|
||||
def to_endtime(self):
|
||||
return self.calendar.to_endtime(self)
|
||||
#@-node:to_endtime
|
||||
#@+node:__cmp__
|
||||
def __cmp__(self, other):
|
||||
return cmp(int(self), int(self.__class__(other)))
|
||||
#@-node:__cmp__
|
||||
#@+node:__add__
|
||||
def __add__(self, other):
|
||||
try:
|
||||
return self.__class__(int(self) + int(self._minutes(other)))
|
||||
except ValueError, e:
|
||||
raise e
|
||||
except:
|
||||
return NotImplemented
|
||||
#@-node:__add__
|
||||
#@+node:__sub__
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, (datetime.timedelta, str, _Minutes)):
|
||||
try:
|
||||
other = self._minutes(other)
|
||||
except:
|
||||
pass
|
||||
|
||||
if isinstance(other, self._minutes):
|
||||
return self.__class__(int(self) - int(other))
|
||||
|
||||
try:
|
||||
return self._minutes(int(self) - int(self.__class__(other)))
|
||||
except:
|
||||
return NotImplemented
|
||||
#@-node:__sub__
|
||||
#@+node:strftime
|
||||
def strftime(self, format=None):
|
||||
return strftime(self.to_datetime(), format or self.STR_FORMAT)
|
||||
#@-node:strftime
|
||||
#@-others
|
||||
#@-node:class _WorkingDateBase
|
||||
#@+node:class Calendar
|
||||
class Calendar(object):
|
||||
"""
|
||||
A calendar to specify working times and vacations.
|
||||
The calendars epoch start at 1.1.1979
|
||||
"""
|
||||
#@ << declarations >>
|
||||
#@+node:<< declarations >>
|
||||
# january the first must be a monday
|
||||
EPOCH = datetime.datetime(1979, 1, 1)
|
||||
minimum_time_unit = DEFAULT_MINIMUM_TIME_UNIT
|
||||
working_days_per_week = DEFAULT_WORKING_DAYS_PER_WEEK
|
||||
working_days_per_month = DEFAULT_WORKING_DAYS_PER_MONTH
|
||||
working_days_per_year = DEFAULT_WORKING_DAYS_PER_YEAR
|
||||
working_hours_per_day = DEFAULT_WORKING_HOURS_PER_DAY
|
||||
now = EPOCH
|
||||
|
||||
|
||||
#@-node:<< declarations >>
|
||||
#@nl
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self):
|
||||
|
||||
self.time_spans = ()
|
||||
self._dt_num_can = ()
|
||||
self._num_dt_can = ()
|
||||
self.working_times = { }
|
||||
self._recalc_working_time()
|
||||
self._make_classes()
|
||||
#@-node:__init__
|
||||
#@+node:__or__
|
||||
def __or__(self, other):
|
||||
if isinstance(other, Calendar):
|
||||
return union(self, other)
|
||||
|
||||
return NotImplemented
|
||||
#@nonl
|
||||
#@-node:__or__
|
||||
#@+node:clone
|
||||
def clone(self):
|
||||
result = Calendar()
|
||||
result.working_times = self.working_times.copy()
|
||||
result.time_spans = self.time_spans
|
||||
result._recalc_working_time()
|
||||
result._build_mapping()
|
||||
return result
|
||||
#@nonl
|
||||
#@-node:clone
|
||||
#@+node:set_working_days
|
||||
def set_working_days(self, day_range, trange, *further_tranges):
|
||||
"""
|
||||
Sets the working days of an calendar
|
||||
day_range is a string of day abbreviations like 'mon, tue'
|
||||
trange and further_tranges is a time range string like
|
||||
'8:00-10:00'
|
||||
"""
|
||||
time_ranges = [ trange ] + list(further_tranges)
|
||||
time_ranges = filter(bool, map(to_time_range, time_ranges))
|
||||
days = _to_days(day_range)
|
||||
|
||||
for k in days.keys():
|
||||
self.working_times[k] = time_ranges
|
||||
|
||||
self._recalc_working_time()
|
||||
self._build_mapping()
|
||||
#@-node:set_working_days
|
||||
#@+node:set_vacation
|
||||
def set_vacation(self, value):
|
||||
"""
|
||||
Sets vacation time.
|
||||
value is either a datetime literal or
|
||||
a sequence of items that can be
|
||||
a datetime literals and or pair of datetime literals
|
||||
"""
|
||||
self.time_spans = _add_to_time_spans(self.time_spans, value, True)
|
||||
self._build_mapping()
|
||||
#@-node:set_vacation
|
||||
#@+node:set_extra_work
|
||||
def set_extra_work(self, value):
|
||||
"""
|
||||
Sets extra working time
|
||||
value is either a datetime literal or
|
||||
a sequence of items that can be
|
||||
a datetime literals and or pair of datetime literals
|
||||
"""
|
||||
self.time_spans = _add_to_time_spans(self.time_spans, value, False)
|
||||
self._build_mapping()
|
||||
#@-node:set_extra_work
|
||||
#@+node:from_datetime
|
||||
def from_datetime(self, value):
|
||||
assert(isinstance(value, datetime.datetime))
|
||||
delta = value - self.EPOCH
|
||||
days = delta.days
|
||||
minutes = delta.seconds / 60
|
||||
|
||||
# calculate the weektime
|
||||
weeks = days / 7
|
||||
wtime = self.week_time * weeks
|
||||
|
||||
# calculate the daytime
|
||||
days %= 7
|
||||
dtime = sum(self.day_times[:days])
|
||||
|
||||
# calculate the minute time
|
||||
slots = self.working_times.get(days, DEFAULT_WORKING_DAYS[days])
|
||||
mtime = 0
|
||||
for start, end in slots:
|
||||
if minutes > end:
|
||||
mtime += end - start
|
||||
else:
|
||||
if minutes > start:
|
||||
mtime += minutes - start
|
||||
break
|
||||
|
||||
result = wtime + dtime + mtime
|
||||
|
||||
# map exceptional timespans
|
||||
dt_num_can = self._dt_num_can
|
||||
pos = bisect.bisect(dt_num_can, (value,)) - 1
|
||||
if pos >= 0:
|
||||
start, end, nstart, nend, cend = dt_num_can[pos]
|
||||
if value < end:
|
||||
if nstart < nend:
|
||||
delta = value - start
|
||||
delta = delta.days * 24 * 60 + delta.seconds / 60
|
||||
result = nstart + delta
|
||||
else:
|
||||
result = nstart
|
||||
else:
|
||||
result += (nend - cend) # == (result - cend) + nend
|
||||
|
||||
return result
|
||||
#@-node:from_datetime
|
||||
#@+node:split_time
|
||||
def split_time(self, value):
|
||||
#map exceptional timespans
|
||||
num_dt_can = self._num_dt_can
|
||||
pos = bisect.bisect(num_dt_can, (value, sys.maxint)) - 1
|
||||
if pos >= 0:
|
||||
nstart, nend, start, end, cend = num_dt_can[pos]
|
||||
if value < nend:
|
||||
value = start + datetime.timedelta(minutes=value - nstart)
|
||||
delta = value - self.EPOCH
|
||||
return delta.days / 7, delta.days % 7, delta.seconds / 60, -1
|
||||
else:
|
||||
value += (cend - nend) # (value - nend + cend)
|
||||
#calculate the weeks since the epoch
|
||||
|
||||
weeks = value / self.week_time
|
||||
value %= self.week_time
|
||||
|
||||
#calculate the remaining days
|
||||
days = 0
|
||||
for day_time in self.day_times:
|
||||
if value < day_time: break
|
||||
value -= day_time
|
||||
days += 1
|
||||
|
||||
#calculate the remaining minutes
|
||||
minutes = 0
|
||||
slots = self.working_times.get(days, DEFAULT_WORKING_DAYS[days])
|
||||
index = 0
|
||||
for start, end in slots:
|
||||
delta = end - start
|
||||
if delta > value:
|
||||
minutes = start + value
|
||||
break
|
||||
else:
|
||||
value -= delta
|
||||
|
||||
index += 1
|
||||
|
||||
return weeks, days, minutes, index
|
||||
#@-node:split_time
|
||||
#@+node:to_starttime
|
||||
def to_starttime(self, value):
|
||||
weeks, days, minutes, index = self.split_time(value)
|
||||
return self.EPOCH + datetime.timedelta(weeks=weeks,
|
||||
days=days,
|
||||
minutes=minutes)
|
||||
#@-node:to_starttime
|
||||
#@+node:to_endtime
|
||||
def to_endtime(self, value):
|
||||
return self.to_starttime(value - 1) + datetime.timedelta(minutes=1)
|
||||
#@-node:to_endtime
|
||||
#@+node:get_working_times
|
||||
def get_working_times(self, day):
|
||||
return self.working_times.get(day, DEFAULT_WORKING_DAYS[day])
|
||||
#@-node:get_working_times
|
||||
#@+node:_build_mapping
|
||||
def _build_mapping(self):
|
||||
self._dt_num_can = self._num_dt_can = ()
|
||||
dt_num_can = []
|
||||
num_dt_can = []
|
||||
|
||||
delta = self.Minutes()
|
||||
for start, end, is_free in self.time_spans:
|
||||
cstart = self.StartDate(start)
|
||||
cend = self.EndDate(end)
|
||||
nstart = cstart + delta
|
||||
|
||||
if not is_free:
|
||||
d = end - start
|
||||
d = d.days * 24 * 60 + d.seconds / 60
|
||||
nend = nstart + d
|
||||
else:
|
||||
nend = nstart
|
||||
|
||||
delta += (nend - nstart) - (cend - cstart)
|
||||
dt_num_can.append((start, end, nstart, nend, cend))
|
||||
num_dt_can.append((nstart, nend, start, end, cend))
|
||||
|
||||
self._dt_num_can = tuple(dt_num_can)
|
||||
self._num_dt_can = tuple(num_dt_can)
|
||||
|
||||
#@-node:_build_mapping
|
||||
#@+node:_recalc_working_time
|
||||
def _recalc_working_time(self):
|
||||
def slot_sum_time(day):
|
||||
slots = self.working_times.get(day, DEFAULT_WORKING_DAYS[day])
|
||||
return sum(map(lambda slot: slot[1] - slot[0], slots))
|
||||
|
||||
self.day_times = map(slot_sum_time, range(0, 7))
|
||||
self.week_time = sum(self.day_times)
|
||||
|
||||
#@-node:_recalc_working_time
|
||||
#@+node:_make_classes
|
||||
def _make_classes(self):
|
||||
#ensure that the clases are instance specific
|
||||
class minutes(_Minutes):
|
||||
calendar = self
|
||||
__slots__ = ()
|
||||
|
||||
class db(_WorkingDateBase):
|
||||
calendar = self
|
||||
_minutes = minutes
|
||||
__slots__ = ()
|
||||
|
||||
class wdt(db): __slots__ = ()
|
||||
class edt(db):
|
||||
__slots__ = ()
|
||||
|
||||
def to_datetime(self):
|
||||
return self.to_endtime()
|
||||
|
||||
self.Minutes, self.StartDate, self.EndDate = minutes, wdt, edt
|
||||
|
||||
self.WorkingDate = self.StartDate
|
||||
|
||||
#@-node:_make_classes
|
||||
#@-others
|
||||
|
||||
|
||||
_default_calendar = Calendar()
|
||||
|
||||
WorkingDate = _default_calendar.WorkingDate
|
||||
StartDate = _default_calendar.StartDate
|
||||
EndDate = _default_calendar.EndDate
|
||||
Minutes = _default_calendar.Minutes
|
||||
#@-node:class Calendar
|
||||
#@-others
|
||||
|
||||
if __name__ == '__main__':
|
||||
cal = Calendar()
|
||||
|
||||
start = EndDate("10.1.2005")
|
||||
print "start", start.strftime(), type(start)
|
||||
|
||||
delay = Minutes("4H")
|
||||
print "delay", delay, delay.strftime()
|
||||
|
||||
print "Start", cal.StartDate is StartDate
|
||||
print "base", cal.StartDate.__bases__[0] == StartDate.__bases__[0]
|
||||
print "type", type(start)
|
||||
|
||||
print "convert start"
|
||||
start2 = cal.StartDate(start)
|
||||
print "convert end"
|
||||
|
||||
start3 = cal.StartDate("10.1.2005")
|
||||
print "start2", start2.strftime(), type(start2)
|
||||
#@-node:@file pcalendar.py
|
||||
#@-leo
|
|
@ -0,0 +1,54 @@
|
|||
############################################################################
|
||||
# Copyright (C) 2005 by Reithinger GmbH
|
||||
# mreithinger@web.de
|
||||
#
|
||||
# This file is part of faces.
|
||||
#
|
||||
# faces is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# faces 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
############################################################################
|
||||
|
||||
import gettext
|
||||
import os.path
|
||||
import locale
|
||||
|
||||
|
||||
def _get_translation():
|
||||
try:
|
||||
return gettext.translation("faces")
|
||||
except:
|
||||
try:
|
||||
if sys.frozen:
|
||||
path = os.path.dirname(sys.argv[0])
|
||||
path = os.path.join(path, "resources", "faces", "locale")
|
||||
else:
|
||||
path = os.path.split(__file__)[0]
|
||||
path = os.path.join(path, "locale")
|
||||
|
||||
return gettext.translation("faces", path)
|
||||
except Exception, e:
|
||||
return None
|
||||
|
||||
def get_gettext():
|
||||
trans = _get_translation()
|
||||
if trans: return trans.ugettext
|
||||
return lambda msg: msg
|
||||
|
||||
|
||||
def get_encoding():
|
||||
trans = _get_translation()
|
||||
if trans: return trans.charset()
|
||||
return locale.getpreferredencoding()
|
||||
|
|
@ -0,0 +1,866 @@
|
|||
#@+leo-ver=4
|
||||
#@+node:@file resource.py
|
||||
#@@language python
|
||||
#@<< Copyright >>
|
||||
#@+node:<< Copyright >>
|
||||
############################################################################
|
||||
# Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
|
||||
# mreithinger@web.de
|
||||
#
|
||||
# This file is part of faces.
|
||||
#
|
||||
# faces is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# faces 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
############################################################################
|
||||
|
||||
#@-node:<< Copyright >>
|
||||
#@nl
|
||||
#@<< Imports >>
|
||||
#@+node:<< Imports >>
|
||||
import pcalendar
|
||||
import datetime
|
||||
import utils
|
||||
import string
|
||||
import bisect
|
||||
import plocale
|
||||
#@-node:<< Imports >>
|
||||
#@nl
|
||||
|
||||
_is_source = True
|
||||
_to_datetime = pcalendar.to_datetime
|
||||
_ = plocale.get_gettext()
|
||||
|
||||
#@+others
|
||||
#@+node:_isattrib
|
||||
#@+doc
|
||||
#@nonl
|
||||
# is used to find snapshot attributes
|
||||
#@-doc
|
||||
#@@code
|
||||
def _isattrib(obj, a):
|
||||
return a[0] != "_" \
|
||||
and not callable(getattr(obj, a)) \
|
||||
and not a.endswith("_members") \
|
||||
and a not in ("name")
|
||||
#@-node:_isattrib
|
||||
#@+node:class ResourceCalendar
|
||||
class ResourceCalendar(object):
|
||||
"""
|
||||
The resource calendar saves the load time of a resource.
|
||||
Is ia sequence of time intervals of loads. An example of
|
||||
such a sequence is:
|
||||
[ (datetime.min, 0),
|
||||
(2006/1/1, 1.0),
|
||||
(2006/1/10, 0.5),
|
||||
(2006/1/15, 0) ]
|
||||
|
||||
That means the resource:
|
||||
is free till january the first 2006
|
||||
is fully booked from january the first to january 10th
|
||||
is half booked from january 10th to january 15th
|
||||
is free since january 15th
|
||||
"""
|
||||
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, src=None):
|
||||
if src:
|
||||
self.bookings = list(src.bookings)
|
||||
else:
|
||||
self.bookings = [ (datetime.datetime.min, 0) ]
|
||||
#@-node:__init__
|
||||
#@+node:__str__
|
||||
def __str__(self):
|
||||
return str(self.bookings)
|
||||
#@-node:__str__
|
||||
#@+node:__repr__
|
||||
def __repr__(self):
|
||||
return "<ResourceCalendar %s>" % (str(self))
|
||||
#@-node:__repr__
|
||||
#@+node:add_load
|
||||
def add_load(self, start, end, load):
|
||||
start = _to_datetime(start)
|
||||
end = _to_datetime(end)
|
||||
|
||||
bookings = self.bookings
|
||||
|
||||
# the load will be converted in an integer to avoid
|
||||
# rouning problems
|
||||
load = int(load * 10000)
|
||||
|
||||
start_item = (start, 0)
|
||||
start_pos = bisect.bisect_left(bookings, start_item)
|
||||
|
||||
left_load = 0
|
||||
left_load = bookings[start_pos - 1][1]
|
||||
|
||||
if start_pos < len(bookings) and bookings[start_pos][0] == start:
|
||||
prev_load = bookings[start_pos][1]
|
||||
if prev_load + load == left_load:
|
||||
del bookings[start_pos]
|
||||
else:
|
||||
bookings[start_pos] = (start, prev_load + load)
|
||||
start_pos += 1
|
||||
else:
|
||||
bookings.insert(start_pos, (start, load + left_load))
|
||||
start_pos += 1
|
||||
|
||||
item = (datetime.datetime.min, 0)
|
||||
for i in range(start_pos, len(bookings)):
|
||||
end_pos = i
|
||||
item = bookings[i]
|
||||
if item[0] >= end: break
|
||||
bookings[i] = (item[0], item[1] + load)
|
||||
else:
|
||||
end_pos = len(bookings)
|
||||
|
||||
left_load = bookings[end_pos - 1][1]
|
||||
if item[0] == end:
|
||||
if item[1] == left_load:
|
||||
del bookings[end_pos]
|
||||
else:
|
||||
bookings.insert(end_pos, (end, left_load - load))
|
||||
#@-node:add_load
|
||||
#@+node:end_of_booking_interval
|
||||
def end_of_booking_interval(self, date):
|
||||
date = _to_datetime(date)
|
||||
bookings = self.bookings
|
||||
date_item = (date, 999999)
|
||||
date_pos = bisect.bisect_left(bookings, date_item) - 1
|
||||
next_date = datetime.datetime.max
|
||||
load = 0
|
||||
|
||||
try:
|
||||
book_item = bookings[date_pos]
|
||||
load = bookings[date_pos][1] / 10000.0
|
||||
next_date = bookings[date_pos + 1][0]
|
||||
except:
|
||||
pass
|
||||
|
||||
return next_date, load
|
||||
#@-node:end_of_booking_interval
|
||||
#@+node:find_free_time
|
||||
def find_free_time(self, start, length, load, max_load):
|
||||
bookings = self.bookings
|
||||
|
||||
if isinstance(start, datetime.datetime):
|
||||
adjust_date = _to_datetime
|
||||
else:
|
||||
adjust_date = start.calendar.EndDate
|
||||
|
||||
start = _to_datetime(start)
|
||||
load = int(load * 10000)
|
||||
max_load = int(max_load * 10000)
|
||||
lb = len(bookings)
|
||||
|
||||
def next_possible(index):
|
||||
while index < lb:
|
||||
sd, lo = bookings[index]
|
||||
if lo + load <= max_load:
|
||||
break
|
||||
|
||||
index += 1
|
||||
|
||||
sd = adjust_date(max(start, sd))
|
||||
ed = sd + length
|
||||
end = _to_datetime(ed)
|
||||
|
||||
index += 1
|
||||
while index < lb:
|
||||
date, lo = bookings[index]
|
||||
|
||||
if date >= end:
|
||||
#I found a good start date
|
||||
return None, sd
|
||||
|
||||
if lo + load > max_load:
|
||||
return index + 1, None
|
||||
|
||||
index += 1
|
||||
|
||||
return None, sd
|
||||
|
||||
start_item = (start, 1000000)
|
||||
i = bisect.bisect_left(bookings, start_item) - 1
|
||||
|
||||
next_start = None
|
||||
while not next_start and i < lb:
|
||||
i, next_start = next_possible(i)
|
||||
|
||||
assert(next_start is not None)
|
||||
return next_start
|
||||
#@-node:find_free_time
|
||||
#@+node:get_bookings
|
||||
def get_bookings(self, start, end):
|
||||
start = _to_datetime(start)
|
||||
end = _to_datetime(end)
|
||||
bookings = self.bookings
|
||||
start_item = (start, 0)
|
||||
start_pos = bisect.bisect_left(bookings, start_item)
|
||||
if start_pos >= len(bookings) or bookings[start_pos][0] > start:
|
||||
start_pos -= 1
|
||||
|
||||
end_item = (end, 0)
|
||||
end_pos = bisect.bisect_left(bookings, end_item)
|
||||
return start_pos, end_pos, bookings
|
||||
#@-node:get_bookings
|
||||
#@+node:get_load
|
||||
def get_load(self, date):
|
||||
date = _to_datetime(date)
|
||||
bookings = self.bookings
|
||||
item = (date, 100000)
|
||||
pos = bisect.bisect_left(bookings, item) - 1
|
||||
return bookings[pos][1] / 10000.0
|
||||
#@-node:get_load
|
||||
#@-others
|
||||
#@-node:class ResourceCalendar
|
||||
#@+node:class _ResourceBase
|
||||
class _ResourceBase(object):
|
||||
pass
|
||||
|
||||
#@-node:class _ResourceBase
|
||||
#@+node:class _MetaResource
|
||||
class _MetaResource(type):
|
||||
doc_template = """
|
||||
A resource class. The resources default attributes can
|
||||
be changed when the class ist instanciated, i.e.
|
||||
%(name)s(max_load=2.0)
|
||||
|
||||
@var max_load:
|
||||
Specify the maximal allowed load sum of all simultaneously
|
||||
allocated tasks of a resource. A ME{max_load} of 1.0 (default)
|
||||
means the resource may be fully allocated. A ME{max_load} of 1.3
|
||||
means the resource may be allocated with 30%% overtime.
|
||||
|
||||
@var title:
|
||||
Specifies an alternative more descriptive name for the task.
|
||||
|
||||
@var efficiency:
|
||||
The efficiency of a resource can be used for two purposes. First
|
||||
you can use it as a crude way to model a team. A team of 5 people
|
||||
should have an efficiency of 5.0. Keep in mind that you cannot
|
||||
track the member of the team individually if you use this
|
||||
feature. The other use is to model performance variations between
|
||||
your resources.
|
||||
|
||||
@var vacation:
|
||||
Specifies the vacation of the resource. This attribute is
|
||||
specified as a list of date literals or date literal intervals.
|
||||
Be aware that the end of an interval is excluded, i.e. it is
|
||||
the first working date.
|
||||
"""
|
||||
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, name, bases, dict_):
|
||||
super(_MetaResource, self).__init__(name, bases, dict_)
|
||||
self.name = name
|
||||
self.title = dict_.get("title", name)
|
||||
self._calendar = { None: ResourceCalendar() }
|
||||
self._tasks = { }
|
||||
self.__set_vacation()
|
||||
self.__add_resource(bases[0])
|
||||
self.__doc__ = dict_.get("__doc__", self.doc_template) % locals()
|
||||
#@-node:__init__
|
||||
#@+node:__or__
|
||||
def __or__(self, other):
|
||||
return self().__or__(other)
|
||||
#@-node:__or__
|
||||
#@+node:__and__
|
||||
def __and__(self, other):
|
||||
return self().__and__(other)
|
||||
#@-node:__and__
|
||||
#@+node:__cmp__
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.name, getattr(other, "name", None))
|
||||
#@-node:__cmp__
|
||||
#@+node:__repr__
|
||||
def __repr__(self):
|
||||
return "<Resource %s>" % self.name
|
||||
#@-node:__repr__
|
||||
#@+node:__str__
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
#@-node:__str__
|
||||
#@+node:__set_vacation
|
||||
def __set_vacation(self):
|
||||
vacation = self.vacation
|
||||
|
||||
if isinstance(vacation, (tuple, list)):
|
||||
for v in vacation:
|
||||
if isinstance(v, (tuple, list)):
|
||||
self.add_vacation(v[0], v[1])
|
||||
else:
|
||||
self.add_vacation(v)
|
||||
else:
|
||||
self.add_vacation(vacation)
|
||||
#@-node:__set_vacation
|
||||
#@+node:__add_resource
|
||||
def __add_resource(self, base):
|
||||
if issubclass(base, _ResourceBase):
|
||||
members = getattr(base, base.__name__ + "_members", [])
|
||||
members.append(self)
|
||||
setattr(base, base.__name__ + "_members", members)
|
||||
#@-node:__add_resource
|
||||
#@+node:get_members
|
||||
def get_members(self):
|
||||
return getattr(self, self.__name__ + "_members", [])
|
||||
#@-node:get_members
|
||||
#@+node:add_vacation
|
||||
def add_vacation(self, start, end=None):
|
||||
start_date = _to_datetime(start)
|
||||
|
||||
if not end:
|
||||
end_date = start_date.replace(hour=23, minute=59)
|
||||
else:
|
||||
end_date = _to_datetime(end)
|
||||
|
||||
for cal in self._calendar.itervalues():
|
||||
cal.add_load(start_date, end_date, 1)
|
||||
|
||||
tp = Booking()
|
||||
tp.start = start_date
|
||||
tp.end = end_date
|
||||
tp.book_start = start_date
|
||||
tp.book_end = end_date
|
||||
tp.work_time = end_date - start_date
|
||||
tp.load = 1.0
|
||||
tp.name = tp.title = _("(vacation)")
|
||||
tp._id = ""
|
||||
self._tasks.setdefault("", []).append(tp)
|
||||
#@-node:add_vacation
|
||||
#@+node:calendar
|
||||
def calendar(self, scenario):
|
||||
try:
|
||||
return self._calendar[scenario]
|
||||
except KeyError:
|
||||
cal = self._calendar[scenario] = ResourceCalendar(self._calendar[None])
|
||||
return cal
|
||||
|
||||
#@-node:calendar
|
||||
#@-others
|
||||
#@-node:class _MetaResource
|
||||
#@+node:make_team
|
||||
def make_team(resource):
|
||||
members = resource.get_members()
|
||||
if not members:
|
||||
return resource
|
||||
|
||||
result = make_team(members[0])
|
||||
for r in members[1:]:
|
||||
result = result & make_team(r)
|
||||
|
||||
return result
|
||||
#@-node:make_team
|
||||
#@+node:class Booking
|
||||
class Booking(object):
|
||||
"""
|
||||
A booking unit for a task.
|
||||
"""
|
||||
#@ << declarations >>
|
||||
#@+node:<< declarations >>
|
||||
book_start = datetime.datetime.min
|
||||
book_end = datetime.datetime.max
|
||||
actual = False
|
||||
_id = ""
|
||||
|
||||
#@-node:<< declarations >>
|
||||
#@nl
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, task=None):
|
||||
self.__task = task
|
||||
#@-node:__init__
|
||||
#@+node:__cmp__
|
||||
def __cmp__(self, other):
|
||||
return cmp(self._id, other._id)
|
||||
#@-node:__cmp__
|
||||
#@+node:path
|
||||
def path(self):
|
||||
first_dot = self._id.find(".")
|
||||
return "root" + self._id[first_dot:]
|
||||
|
||||
path = property(path)
|
||||
#@nonl
|
||||
#@-node:path
|
||||
#@+node:_idendity_
|
||||
def _idendity_(self):
|
||||
return self._id
|
||||
#@-node:_idendity_
|
||||
#@+node:__getattr__
|
||||
def __getattr__(self, name):
|
||||
if self.__task:
|
||||
return getattr(self.__task, name)
|
||||
|
||||
raise AttributeError("'%s' is not a valid attribute" % (name))
|
||||
#@-node:__getattr__
|
||||
#@-others
|
||||
#@-node:class Booking
|
||||
#@+node:class ResourceList
|
||||
class ResourceList(list):
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, *args):
|
||||
if args: self.extend(args)
|
||||
#@-node:__init__
|
||||
#@-others
|
||||
#@-node:class ResourceList
|
||||
#@+node:class Resource
|
||||
class Resource(_ResourceBase):
|
||||
#@ << declarations >>
|
||||
#@+node:<< declarations >>
|
||||
__metaclass__ = _MetaResource
|
||||
__attrib_completions__ = {\
|
||||
"max_load": 'max_load = ',
|
||||
"title": 'title = "|"',
|
||||
"efficiency": 'efficiency = ',
|
||||
"vacation": 'vacation = [("|2002-02-01", "2002-02-05")]' }
|
||||
|
||||
__type_image__ = "resource16"
|
||||
|
||||
max_load = None # the maximum sum load for all task
|
||||
vacation = ()
|
||||
efficiency = 1.0
|
||||
|
||||
|
||||
#@-node:<< declarations >>
|
||||
#@nl
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
#@-node:__init__
|
||||
#@+node:_idendity_
|
||||
def _idendity_(cls):
|
||||
return "resource:" + cls.__name__
|
||||
|
||||
_idendity_ = classmethod(_idendity_)
|
||||
#@-node:_idendity_
|
||||
#@+node:__repr__
|
||||
def __repr__(self):
|
||||
return "<Resource %s>" % self.__class__.__name__
|
||||
#@-node:__repr__
|
||||
#@+node:__str__
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
#@-node:__str__
|
||||
#@+node:__call__
|
||||
def __call__(self):
|
||||
return self
|
||||
#@-node:__call__
|
||||
#@+node:__hash__
|
||||
def __hash__(self):
|
||||
return hash(self.__class__)
|
||||
#@-node:__hash__
|
||||
#@+node:__cmp__
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.name, other.name)
|
||||
#@-node:__cmp__
|
||||
#@+node:__or__
|
||||
def __or__(self, other):
|
||||
if type(other) is _MetaResource:
|
||||
other = other()
|
||||
|
||||
result = Resource()
|
||||
result._subresource = _OrResourceGroup(self, other)
|
||||
return result
|
||||
#@-node:__or__
|
||||
#@+node:__and__
|
||||
def __and__(self, other):
|
||||
if type(other) is _MetaResource:
|
||||
other = other()
|
||||
|
||||
result = Resource()
|
||||
result._subresource = _AndResourceGroup(self, other)
|
||||
return result
|
||||
#@-node:__and__
|
||||
#@+node:_permutation_count
|
||||
def _permutation_count(self):
|
||||
if hasattr(self, "_subresource"):
|
||||
return self._subresource._permutation_count()
|
||||
|
||||
return 1
|
||||
#@-node:_permutation_count
|
||||
#@+node:_get_resources
|
||||
def _get_resources(self, state):
|
||||
if hasattr(self, "_subresource"):
|
||||
result = self._subresource._get_resources(state)
|
||||
|
||||
if self.name != "Resource":
|
||||
result.name = self.name
|
||||
|
||||
if self.title != "Resource":
|
||||
result.title = self.title
|
||||
|
||||
return result
|
||||
|
||||
result = ResourceList(self)
|
||||
return result
|
||||
#@-node:_get_resources
|
||||
#@+node:all_members
|
||||
def all_members(self):
|
||||
if hasattr(self, "_subresource"):
|
||||
return self._subresource.all_members()
|
||||
|
||||
return [ self.__class__ ]
|
||||
#@-node:all_members
|
||||
#@+node:unbook_tasks_of_project
|
||||
def unbook_tasks_of_project(cls, project_id, scenario):
|
||||
try:
|
||||
task_list = cls._tasks[scenario]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
add_load = cls.calendar(scenario).add_load
|
||||
for task_id, bookings in task_list.items():
|
||||
if task_id.startswith(project_id):
|
||||
for item in bookings:
|
||||
add_load(item.book_start, item.book_end, -item.load)
|
||||
|
||||
del task_list[task_id]
|
||||
|
||||
if not task_list:
|
||||
del cls._tasks[scenario]
|
||||
|
||||
unbook_tasks_of_project = classmethod(unbook_tasks_of_project)
|
||||
#@-node:unbook_tasks_of_project
|
||||
#@+node:unbook_task
|
||||
def unbook_task(cls, task):
|
||||
identdity = task._idendity_()
|
||||
scenario = task.scenario
|
||||
|
||||
try:
|
||||
task_list = cls._tasks[scenario]
|
||||
bookings = task_list[identdity]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
add_load = cls.calendar(scenario).add_load
|
||||
for b in bookings:
|
||||
add_load(b.book_start, b.book_end, -b.load)
|
||||
|
||||
del task_list[identdity]
|
||||
if not task_list:
|
||||
del cls._tasks[scenario]
|
||||
|
||||
unbook_task = classmethod(unbook_task)
|
||||
#@-node:unbook_task
|
||||
#@+node:correct_bookings
|
||||
def correct_bookings(cls, task):
|
||||
#correct the booking data with the actual task data
|
||||
try:
|
||||
tasks = cls._tasks[task.scenario][task._idendity_()]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
for t in tasks:
|
||||
t.start = task.start.to_datetime()
|
||||
t.end = task.end.to_datetime()
|
||||
|
||||
correct_bookings = classmethod(correct_bookings)
|
||||
#@-node:correct_bookings
|
||||
#@+node:book_task
|
||||
def book_task(cls, task, start, end, load, work_time, actual):
|
||||
if not work_time: return
|
||||
|
||||
start = _to_datetime(start)
|
||||
end = _to_datetime(end)
|
||||
|
||||
identdity = task._idendity_()
|
||||
task_list = cls._tasks.setdefault(task.scenario, {})
|
||||
bookings = task_list.setdefault(identdity, [])
|
||||
add_load = cls.calendar(task.scenario).add_load
|
||||
|
||||
tb = Booking(task)
|
||||
tb.book_start = start
|
||||
tb.book_end = end
|
||||
tb._id = identdity
|
||||
tb.load = load
|
||||
tb.start = _to_datetime(task.start)
|
||||
tb.end = _to_datetime(task.end)
|
||||
tb.title = task.title
|
||||
tb.name = task.name
|
||||
tb.work_time = int(work_time)
|
||||
tb.actual = actual
|
||||
bookings.append(tb)
|
||||
result = add_load(start, end, load)
|
||||
return result
|
||||
|
||||
book_task = classmethod(book_task)
|
||||
#@-node:book_task
|
||||
#@+node:length_of
|
||||
def length_of(cls, task):
|
||||
cal = task.root.calendar
|
||||
bookings = cls.get_bookings(task)
|
||||
return sum(map(lambda b: task._to_delta(b.work_time).round(), bookings))
|
||||
|
||||
length_of = classmethod(length_of)
|
||||
#@-node:length_of
|
||||
#@+node:done_of
|
||||
def done_of(self, task):
|
||||
cal = task.root.calendar
|
||||
now = cal.now
|
||||
bookings = self.get_bookings(task)
|
||||
|
||||
if task.__dict__.has_key("effort"):
|
||||
efficiency = self.efficiency * task.efficiency
|
||||
else:
|
||||
efficiency = 1
|
||||
|
||||
def book_done(booking):
|
||||
if booking.book_start >= now:
|
||||
return 0
|
||||
|
||||
factor = 1
|
||||
if booking.book_end > now:
|
||||
start = task._to_start(booking.book_start)
|
||||
end = task._to_end(booking.book_end)
|
||||
cnow = task._to_start(now)
|
||||
factor = float(cnow - start) / ((end - start) or 1)
|
||||
|
||||
return factor * booking.work_time * efficiency
|
||||
|
||||
return task._to_delta(sum(map(book_done, bookings)))
|
||||
#@-node:done_of
|
||||
#@+node:todo_of
|
||||
def todo_of(self, task):
|
||||
cal = task.root.calendar
|
||||
now = cal.now
|
||||
|
||||
bookings = self.get_bookings(task)
|
||||
if task.__dict__.has_key("effort"):
|
||||
efficiency = self.efficiency * task.efficiency
|
||||
else:
|
||||
efficiency = 1
|
||||
|
||||
def book_todo(booking):
|
||||
if booking.book_end <= now:
|
||||
return 0
|
||||
|
||||
factor = 1
|
||||
if booking.book_start < now:
|
||||
start = task._to_start(booking.book_start)
|
||||
end = task._to_end(booking.book_end)
|
||||
cnow = task._to_start(now)
|
||||
factor = float(end - cnow) / ((end - start) or 1)
|
||||
|
||||
return factor * booking.work_time * efficiency
|
||||
|
||||
return task._to_delta(sum(map(book_todo, bookings)))
|
||||
#@-node:todo_of
|
||||
#@+node:get_bookings
|
||||
def get_bookings(cls, task):
|
||||
return cls._tasks.get(task.scenario, {}).get(task._idendity_(), ())
|
||||
|
||||
get_bookings = classmethod(get_bookings)
|
||||
#@-node:get_bookings
|
||||
#@+node:get_bookings_at
|
||||
def get_bookings_at(cls, start, end, scenario):
|
||||
result = []
|
||||
|
||||
try:
|
||||
items = cls._tasks[scenario].iteritems()
|
||||
except KeyError:
|
||||
return ()
|
||||
|
||||
for task_id, bookings in items:
|
||||
result += [ booking for booking in bookings
|
||||
if booking.book_start < end
|
||||
and booking.book_end > start ]
|
||||
|
||||
vacations = cls._tasks.get("", ())
|
||||
result += [ booking for booking in vacations
|
||||
if booking.book_start < end
|
||||
and booking.book_end > start ]
|
||||
|
||||
return result
|
||||
|
||||
get_bookings_at = classmethod(get_bookings_at)
|
||||
#@-node:get_bookings_at
|
||||
#@+node:find_free_time
|
||||
def find_free_time(cls, start, length, load, max_load, scenario):
|
||||
return cls.calendar(scenario).find_free_time(start, length, load, max_load)
|
||||
|
||||
find_free_time = classmethod(find_free_time)
|
||||
#@-node:find_free_time
|
||||
#@+node:get_load
|
||||
def get_load(cls, date, scenario):
|
||||
return cls.calendar(scenario).get_load(date)
|
||||
|
||||
get_load = classmethod(get_load)
|
||||
#@-node:get_load
|
||||
#@+node:end_of_booking_interval
|
||||
def end_of_booking_interval(cls, date, task):
|
||||
return cls.calendar(task.scenario).end_of_booking_interval(date)
|
||||
|
||||
end_of_booking_interval = classmethod(end_of_booking_interval)
|
||||
#@-node:end_of_booking_interval
|
||||
#@+node:snapshot
|
||||
def snapshot(self):
|
||||
from task import _as_string
|
||||
def isattrib(a):
|
||||
if a == "max_load" and self.max_load is None: return False
|
||||
if a in ("name", "title", "vacation"): return False
|
||||
return _isattrib(self, a)
|
||||
|
||||
attribs = filter(isattrib, dir(self))
|
||||
attribs = map(lambda a: "%s=%s" % (a, _as_string(getattr(self, a))),
|
||||
attribs)
|
||||
|
||||
return self.name + "(%s)" % ", ".join(attribs)
|
||||
#@-node:snapshot
|
||||
#@-others
|
||||
#@-node:class Resource
|
||||
#@+node:class _ResourceGroup
|
||||
|
||||
|
||||
class _ResourceGroup(object):
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, *args):
|
||||
self.resources = []
|
||||
for a in args:
|
||||
self.__append(a)
|
||||
#@-node:__init__
|
||||
#@+node:all_members
|
||||
def all_members(self):
|
||||
group = reduce(lambda a, b: a + b.all_members(),
|
||||
self.resources, [])
|
||||
group = map(lambda r: (r, True), group)
|
||||
group = dict(group)
|
||||
group = group.keys()
|
||||
return group
|
||||
#@-node:all_members
|
||||
#@+node:_permutation_count
|
||||
def _permutation_count(self):
|
||||
abstract
|
||||
#@-node:_permutation_count
|
||||
#@+node:_refactor
|
||||
def _refactor(self, arg):
|
||||
pass
|
||||
#@-node:_refactor
|
||||
#@+node:__append
|
||||
def __append(self, arg):
|
||||
if isinstance(arg, self.__class__):
|
||||
self.resources += arg.resources
|
||||
for r in arg.resources:
|
||||
self._refactor(r)
|
||||
return
|
||||
elif isinstance(arg, Resource):
|
||||
subresources = getattr(arg, "_subresource", None)
|
||||
if subresources:
|
||||
self.__append(subresources)
|
||||
return
|
||||
else:
|
||||
self.resources.append(arg)
|
||||
else:
|
||||
assert(isinstance(arg, _ResourceGroup))
|
||||
self.resources.append(arg)
|
||||
|
||||
self._refactor(arg)
|
||||
#@-node:__append
|
||||
#@+node:__str__
|
||||
def __str__(self):
|
||||
op = lower(self.__class__.__name__[0:-13])
|
||||
return "(" + \
|
||||
string.join([str(r) for r in self.resources],
|
||||
" " + op + " ") + \
|
||||
")"
|
||||
#@-node:__str__
|
||||
#@-others
|
||||
#@-node:class _ResourceGroup
|
||||
#@+node:class _OrResourceGroup
|
||||
|
||||
|
||||
class _OrResourceGroup(_ResourceGroup):
|
||||
#@ @+others
|
||||
#@+node:_get_resources
|
||||
def _get_resources(self, state):
|
||||
for r in self.resources:
|
||||
c = r._permutation_count()
|
||||
if c <= state:
|
||||
state -= c
|
||||
else:
|
||||
return r._get_resources(state)
|
||||
|
||||
assert(0)
|
||||
#@-node:_get_resources
|
||||
#@+node:_permutation_count
|
||||
def _permutation_count(self):
|
||||
return sum([ r._permutation_count() for r in self.resources])
|
||||
#@-node:_permutation_count
|
||||
#@-others
|
||||
#@-node:class _OrResourceGroup
|
||||
#@+node:class _AndResourceGroup
|
||||
|
||||
|
||||
class _AndResourceGroup(_ResourceGroup):
|
||||
#@ @+others
|
||||
#@+node:__init__
|
||||
def __init__(self, *args):
|
||||
self.factors = [ 1 ]
|
||||
_ResourceGroup.__init__(self, *args)
|
||||
#@-node:__init__
|
||||
#@+node:_refactor
|
||||
def _refactor(self, arg):
|
||||
count = arg._permutation_count()
|
||||
self.factors = [ count * f for f in self.factors ]
|
||||
self.factors.append(1)
|
||||
#@-node:_refactor
|
||||
#@+node:_permutation_count
|
||||
#print "AndResourceGroup", count, arg, self.factors
|
||||
|
||||
|
||||
def _permutation_count(self):
|
||||
return self.factors[0]
|
||||
#@-node:_permutation_count
|
||||
#@+node:_get_resources
|
||||
def _get_resources(self, state):
|
||||
"""delivers None when there are duplicate resources"""
|
||||
result = []
|
||||
for i in range(1, len(self.factors)):
|
||||
f = self.factors[i]
|
||||
substate = state / f
|
||||
state %= f
|
||||
result.append(self.resources[i - 1]._get_resources(substate))
|
||||
|
||||
result = ResourceList(*list(utils.flatten(result)))
|
||||
dupl_test = { }
|
||||
for r in result:
|
||||
if dupl_test.has_key(r):
|
||||
return None
|
||||
else:
|
||||
dupl_test[r] = 1
|
||||
|
||||
return result
|
||||
#@-node:_get_resources
|
||||
#@+node:_has_duplicates
|
||||
def _has_duplicates(self, state):
|
||||
resources = self._get_resources(state)
|
||||
tmp = { }
|
||||
for r in resources:
|
||||
if tmp.has_key(r):
|
||||
return True
|
||||
|
||||
tmp[r] = 1
|
||||
|
||||
return False
|
||||
#@-node:_has_duplicates
|
||||
#@-others
|
||||
#@-node:class _AndResourceGroup
|
||||
#@-others
|
||||
#@-node:@file resource.py
|
||||
#@-leo
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,113 @@
|
|||
############################################################################
|
||||
# Copyright (C) 2005 by Reithinger GmbH
|
||||
# mreithinger@web.de
|
||||
#
|
||||
# This file is part of faces.
|
||||
#
|
||||
# faces is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# faces 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
############################################################################
|
||||
|
||||
import faces.pcalendar as pcal
|
||||
import matplotlib.cbook as cbook
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
|
||||
class TimeScale(object):
|
||||
def __init__(self, calendar):
|
||||
self.data_calendar = calendar
|
||||
self._create_chart_calendar()
|
||||
self.now = self.to_num(self.data_calendar.now)
|
||||
|
||||
|
||||
def to_datetime(self, xval):
|
||||
return xval.to_datetime()
|
||||
|
||||
|
||||
def to_num(self, date):
|
||||
return self.chart_calendar.WorkingDate(date)
|
||||
|
||||
|
||||
def is_free_slot(self, value):
|
||||
dt1 = self.chart_calendar.to_starttime(value)
|
||||
dt2 = self.data_calendar.to_starttime\
|
||||
(self.data_calendar.from_datetime(dt1))
|
||||
return dt1 != dt2
|
||||
|
||||
|
||||
def is_free_day(self, value):
|
||||
dt1 = self.chart_calendar.to_starttime(value)
|
||||
dt2 = self.data_calendar.to_starttime\
|
||||
(self.data_calendar.from_datetime(dt1))
|
||||
return dt1.date() != dt2.date()
|
||||
|
||||
|
||||
def _create_chart_calendar(self):
|
||||
dcal = self.data_calendar
|
||||
ccal = self.chart_calendar = pcal.Calendar()
|
||||
ccal.minimum_time_unit = 1
|
||||
|
||||
#pad worktime slots of calendar (all days should be equally long)
|
||||
slot_sum = lambda slots: sum(map(lambda slot: slot[1] - slot[0], slots))
|
||||
day_sum = lambda day: slot_sum(dcal.get_working_times(day))
|
||||
|
||||
max_work_time = max(map(day_sum, range(7)))
|
||||
|
||||
#working_time should have 2/3
|
||||
sum_time = 3 * max_work_time / 2
|
||||
|
||||
#now create timeslots for ccal
|
||||
def create_time_slots(day):
|
||||
src_slots = dcal.get_working_times(day)
|
||||
slots = [0, src_slots, 24*60]
|
||||
slots = tuple(cbook.flatten(slots))
|
||||
slots = zip(slots[:-1], slots[1:])
|
||||
|
||||
#balance non working slots
|
||||
work_time = slot_sum(src_slots)
|
||||
non_work_time = sum_time - work_time
|
||||
|
||||
non_slots = filter(lambda s: s not in src_slots, slots)
|
||||
non_slots = map(lambda s: (s[1] - s[0], s), non_slots)
|
||||
non_slots.sort()
|
||||
|
||||
slots = []
|
||||
i = 0
|
||||
for l, s in non_slots:
|
||||
delta = non_work_time / (len(non_slots) - i)
|
||||
delta = min(l, delta)
|
||||
non_work_time -= delta
|
||||
slots.append((s[0], s[0] + delta))
|
||||
i += 1
|
||||
|
||||
slots.extend(src_slots)
|
||||
slots.sort()
|
||||
return slots
|
||||
|
||||
min_delta = sys.maxint
|
||||
for i in range(7):
|
||||
slots = create_time_slots(i)
|
||||
ccal.working_times[i] = slots
|
||||
min_delta = min(min_delta, min(map(lambda s: s[1] - s[0], slots)))
|
||||
|
||||
ccal._recalc_working_time()
|
||||
|
||||
self.slot_delta = min_delta
|
||||
self.day_delta = sum_time
|
||||
self.week_delta = ccal.week_time
|
||||
|
||||
|
||||
_default_scale = TimeScale(pcal._default_calendar)
|
|
@ -0,0 +1,124 @@
|
|||
############################################################################
|
||||
# Copyright (C) 2005 by Reithinger GmbH
|
||||
# mreithinger@web.de
|
||||
#
|
||||
# This file is part of faces.
|
||||
#
|
||||
# faces is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# faces 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
############################################################################
|
||||
|
||||
import observer
|
||||
import os.path
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
_call_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
|
||||
def get_installation_path():
|
||||
try:
|
||||
if sys.frozen:
|
||||
path = _call_dir
|
||||
else:
|
||||
raise AttributeError()
|
||||
except AttributeError:
|
||||
path = os.path.abspath(observer.__file__)
|
||||
path = os.path.split(path)[0]
|
||||
|
||||
path = os.path.normcase(path)
|
||||
return path
|
||||
|
||||
|
||||
def get_resource_path():
|
||||
try:
|
||||
if sys.frozen:
|
||||
path = _call_dir
|
||||
path = os.path.join(path, "resources", "faces", "gui")
|
||||
else:
|
||||
raise AttributeError()
|
||||
except AttributeError:
|
||||
path = get_installation_path()
|
||||
path = os.path.join(path, "gui", "resources")
|
||||
|
||||
path = os.path.normcase(path)
|
||||
return path
|
||||
|
||||
|
||||
def get_template_path():
|
||||
try:
|
||||
if sys.frozen:
|
||||
path = _call_dir
|
||||
path = os.path.join(path, "resources", "faces", "templates")
|
||||
else:
|
||||
raise AttributeError()
|
||||
except AttributeError:
|
||||
path = get_installation_path()
|
||||
path = os.path.join(path, "templates")
|
||||
|
||||
path = os.path.normcase(path)
|
||||
return path
|
||||
|
||||
|
||||
def get_howtos_path():
|
||||
try:
|
||||
if sys.frozen:
|
||||
path = _call_dir
|
||||
else:
|
||||
raise AttributeError()
|
||||
except AttributeError:
|
||||
path = get_installation_path()
|
||||
|
||||
path = os.path.join(path, "howtos")
|
||||
path = os.path.normcase(path)
|
||||
return path
|
||||
|
||||
|
||||
|
||||
def flatten(items):
|
||||
if isinstance(items, tuple):
|
||||
items = list(items)
|
||||
|
||||
if not isinstance(items, list):
|
||||
yield items
|
||||
|
||||
stack = [iter(items)]
|
||||
while stack:
|
||||
for item in stack[-1]:
|
||||
if isinstance(item, tuple):
|
||||
item = list(item)
|
||||
|
||||
if isinstance(item, list):
|
||||
stack.append(iter(item))
|
||||
break
|
||||
yield item
|
||||
else:
|
||||
stack.pop()
|
||||
|
||||
|
||||
def do_yield():
|
||||
pass
|
||||
|
||||
|
||||
def progress_start(title, maximum, message=""):
|
||||
pass
|
||||
|
||||
def progress_update(value, message=""):
|
||||
pass
|
||||
|
||||
def progress_end():
|
||||
pass
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue