[FIX] Renamed faces and removed print statements in it

bzr revid: rvo@tinyerp.co.in-20100129074800-cjtob9irn861g2ff
This commit is contained in:
Rvo (Open ERP) 2010-01-29 13:18:00 +05:30
parent 404bf5ca1a
commit 557de24693
8 changed files with 6081 additions and 0 deletions

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

3855
addons/resource/faces/task.py Executable file

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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