diff --git a/addons/project/project.py b/addons/project/project.py
index dcb9d562cfc..024e053dcf3 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -2,14 +2,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, date
-from dateutil import relativedelta
from lxml import etree
import time
from openerp import api
from openerp import SUPERUSER_ID
from openerp import tools
-from openerp.addons.resource.faces import task as Task
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
@@ -315,104 +313,6 @@ class project(osv.osv):
self.setActive(cr, uid, child_ids, value, context=None)
return True
- def _schedule_header(self, cr, uid, ids, force_members=True, context=None):
- context = context or {}
- if type(ids) in (long, int,):
- ids = [ids]
- projects = self.browse(cr, uid, ids, context=context)
-
- for project in projects:
- if (not project.members) and force_members:
- raise UserError(_("You must assign members on the project '%s'!") % (project.name,))
-
- resource_pool = self.pool.get('resource.resource')
-
- result = "from openerp.addons.resource.faces import *\n"
- result += "import datetime\n"
- for project in self.browse(cr, uid, ids, context=context):
- u_ids = [i.id for i in project.members]
- if project.user_id and (project.user_id.id not in u_ids):
- u_ids.append(project.user_id.id)
- for task in project.tasks:
- if task.user_id and (task.user_id.id not in u_ids):
- u_ids.append(task.user_id.id)
- calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
- resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
- for key, vals in resource_objs.items():
- result +='''
-class User_%s(Resource):
- efficiency = %s
-''' % (key, vals.get('efficiency', False))
-
- result += '''
-def Project():
- '''
- return result
-
- def _schedule_project(self, cr, uid, project, context=None):
- resource_pool = self.pool.get('resource.resource')
- calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
- working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
- # TODO: check if we need working_..., default values are ok.
- puids = [x.id for x in project.members]
- if project.user_id:
- puids.append(project.user_id.id)
- result = """
- def Project_%d():
- start = \'%s\'
- working_days = %s
- resource = %s
-""" % (
- project.id,
- project.date_start or time.strftime('%Y-%m-%d'), working_days,
- '|'.join(['User_'+str(x) for x in puids]) or 'None'
- )
- vacation = calendar_id and tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) or False
- if vacation:
- result+= """
- vacation = %s
-""" % ( vacation, )
- return result
-
- #TODO: DO Resource allocation and compute availability
- def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
- if context == None:
- context = {}
- allocation = {}
- return allocation
-
- def schedule_tasks(self, cr, uid, ids, context=None):
- context = context or {}
- if type(ids) in (long, int,):
- ids = [ids]
- projects = self.browse(cr, uid, ids, context=context)
- result = self._schedule_header(cr, uid, ids, False, context=context)
- for project in projects:
- result += self._schedule_project(cr, uid, project, context=context)
- result += self.pool.get('project.task')._generate_task(cr, uid, project.tasks, ident=4, context=context)
-
- local_dict = {}
- exec result in local_dict
- projects_gantt = Task.BalancedProject(local_dict['Project'])
-
- for project in projects:
- project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
- for task in project.tasks:
- if task.stage_id and task.stage_id.fold:
- continue
-
- p = getattr(project_gantt, 'Task_%d' % (task.id,))
-
- self.pool.get('project.task').write(cr, uid, [task.id], {
- 'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
- 'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
- }, context=context)
- if (not task.user_id) and (p.booked_resource):
- self.pool.get('project.task').write(cr, uid, [task.id], {
- 'user_id': int(p.booked_resource[0].name[5:]),
- }, context=context)
- return True
-
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml
index 1c469435af5..fe27119fb4a 100644
--- a/addons/project/project_demo.xml
+++ b/addons/project/project_demo.xml
@@ -387,10 +387,6 @@
-
-
-
project.task
diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py
index d053f2213d3..39a9a864d2a 100644
--- a/addons/project/tests/test_project_flow.py
+++ b/addons/project/tests/test_project_flow.py
@@ -67,9 +67,8 @@ class TestProjectFlow(TestProjectBase):
self.assertEqual(pigs.state, 'close')
# Re-open
pigs.state = 'open'
- # Re-convert into a template and schedule tasks
+ # Re-convert into a template
pigs.set_template()
- pigs.schedule_tasks()
# Copy the project
new_project = pigs.copy()
self.assertEqual(len(new_project.tasks), 2, 'project: copied project should have copied task')
diff --git a/addons/resource/__init__.py b/addons/resource/__init__.py
index b1535240df8..4ca401409b4 100644
--- a/addons/resource/__init__.py
+++ b/addons/resource/__init__.py
@@ -2,4 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import resource
-import faces
diff --git a/addons/resource/faces/__init__.py b/addons/resource/faces/__init__.py
deleted file mode 100644
index 311fa853d12..00000000000
--- a/addons/resource/faces/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-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
diff --git a/addons/resource/faces/observer.py b/addons/resource/faces/observer.py
deleted file mode 100644
index b61db92e6cb..00000000000
--- a/addons/resource/faces/observer.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#@+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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
diff --git a/addons/resource/faces/pcalendar.py b/addons/resource/faces/pcalendar.py
deleted file mode 100644
index c964d751d02..00000000000
--- a/addons/resource/faces/pcalendar.py
+++ /dev/null
@@ -1,951 +0,0 @@
-#@+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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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")
-
- delay = Minutes("4H")
-
- start2 = cal.StartDate(start)
-
- start3 = cal.StartDate("10.1.2005")
-#@-node:@file pcalendar.py
-#@-leo
diff --git a/addons/resource/faces/plocale.py b/addons/resource/faces/plocale.py
deleted file mode 100644
index b6c91fe59a5..00000000000
--- a/addons/resource/faces/plocale.py
+++ /dev/null
@@ -1,54 +0,0 @@
-############################################################################
-# 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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-############################################################################
-
-import gettext
-import os.path
-import locale
-import sys
-
-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()
-
diff --git a/addons/resource/faces/resource.py b/addons/resource/faces/resource.py
deleted file mode 100644
index a7f313ba52b..00000000000
--- a/addons/resource/faces/resource.py
+++ /dev/null
@@ -1,866 +0,0 @@
-#@+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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 "" % (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 "" % 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 "" % 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
diff --git a/addons/resource/faces/task.py b/addons/resource/faces/task.py
deleted file mode 100644
index fde6890ccb7..00000000000
--- a/addons/resource/faces/task.py
+++ /dev/null
@@ -1,3854 +0,0 @@
-#@+leo-ver=4
-#@+node:@file task.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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-############################################################################
-
-#@-node:<< Copyright >>
-#@nl
-"""
-This module contains all classes for project plan objects
-"""
-#@<< Imports >>
-#@+node:<< Imports >>
-import pcalendar
-import resource
-import types
-import sys
-import datetime
-import operator as op
-import warnings
-import locale
-import weakref
-import opcode
-import new
-try:
- set
-except NameError:
- from sets import Set as set
-#@-node:<< Imports >>
-#@nl
-
-_is_source = True
-
-STRICT = 3
-SLOPPY = 2
-SMART = 1
-
-#@+others
-#@+node:Exceptions
-#@+node:class AttributeError
-class AttributeError(AttributeError):
- #@ << class AttributeError declarations >>
- #@+node:<< class AttributeError declarations >>
- is_frozen = False
-
-
- #@-node:<< class AttributeError declarations >>
- #@nl
-#@-node:class AttributeError
-#@+node:class RecursionError
-class RecursionError(Exception):
- """This exception is raised in cas of cirular dependencies
- within an project"""
- #@ << class RecursionError declarations >>
- #@+node:<< class RecursionError declarations >>
- pass
-
-
- #@-node:<< class RecursionError declarations >>
- #@nl
-#@-node:class RecursionError
-#@+node:class _IncompleteError
-class _IncompleteError(Exception):
- """This exception is raised, when there is not enough
- data specified to calculate as task"""
- #@ @+others
- #@+node:__init__
- def __init__(self, *args):
- if isinstance(args[0], (basestring)):
- Exception.__init__(self, *args)
- else:
- Exception.__init__(self,
- "Not enough data for calculating task, "\
- "maybe you have a recursive reference.",
- *args)
- #@-node:__init__
- #@-others
-#@-node:class _IncompleteError
-#@-node:Exceptions
-#@+node:Proxies for self referencing
-#@+node:class _MeProxy
-class _MeProxy(object):
- """
- A Proxy class for the me attribute of tasks in the compile case
- """
- #@ << declarations >>
- #@+node:<< declarations >>
- __slots__ = "task"
-
- #@-node:<< declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, task):
- object.__setattr__(self, "task", task)
- #@-node:__init__
- #@+node:__getattr__
- def __getattr__(self, name):
- if self.task._is_frozen:
- return getattr(self.task, name)
-
- if name in ("name", "up", "root", "path",
- "depth", "index", "calendar",
- "children", "resource", "balance"):
- return getattr(self.task, name)
-
- value = self.task.__dict__.get(name, _NEVER_USED_)
- def make_val(default):
- if value is _NEVER_USED_: return default
- return value
-
- if name in ("start", "end"):
- return self.task._to_start(make_val("1.1.2006"))
-
- if name in ("length", "effort", "duration", "todo", "done",
- "buffer", "performed", "performed_effort",
- "performed_end", "performed_start",
- "performed_work_time" ):
- return self.task._to_delta(make_val("0d"))
-
- if name in ("complete", "priority", "efficiency"):
- return make_val(0)
-
- if value is _NEVER_USED_:
- raise AttributeError("'%s' is not a valid attribute." % (name))
-
- return value
- #@-node:__getattr__
- #@+node:__setattr__
- def __setattr__(self, name, value):
- self.task._set_attrib(name, value)
- #@-node:__setattr__
- #@+node:__iter__
- def __iter__(self):
- return iter(self.task)
- #@nonl
- #@-node:__iter__
- #@+node:add_attrib
- def add_attrib(self, name_or_iter, val=None):
- if not isinstance(name_or_iter, str):
- for n, v in name_or_iter:
- setattr(self, n, v)
- else:
- setattr(self, name_or_iter, val)
- #@-node:add_attrib
- #@-others
-#@nonl
-#@-node:class _MeProxy
-#@+node:class _MeProxyRecalc
-class _MeProxyRecalc(_MeProxy):
- """
- A Proxy class for the me attribute of tasks in the recalc case
- """
- #@ @+others
- #@+node:__setattr__
- def __setattr__(self, name, value):
- if self.task._properties.has_key(name):
- self.task._set_attrib(name, value)
- #@-node:__setattr__
- #@-others
-#@-node:class _MeProxyRecalc
-#@+node:class _MeProxyError
-class _MeProxyError(_MeProxy):
- #@ << declarations >>
- #@+node:<< declarations >>
- __slots__ = ("task", "attrib", "exc")
-
- #@-node:<< declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, task, attrib, exc):
- _MeProxy.__init__(self, task)
- object.__setattr__(self, "attrib", attrib)
- object.__setattr__(self, "exc", exc)
- #@-node:__init__
- #@+node:__setattr__
- def __setattr__(self, name, value):
- if name == self.attrib or not self.attrib:
- raise self.exc
- #@-node:__setattr__
- #@-others
-#@-node:class _MeProxyError
-#@+node:class _MeProxyWarn
-class _MeProxyWarn(_MeProxy):
- #@ << declarations >>
- #@+node:<< declarations >>
- __slots__ = ("task", "attrib", "message")
-
- #@-node:<< declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, task, attrib, message):
- _MeProxy.__init__(self, task)
- object.__setattr__(self, "attrib", attrib)
- object.__setattr__(self, "message", message)
- #@-node:__init__
- #@+node:__setattr__
- def __setattr__(self, name, value):
- if name == self.attrib or not self.attrib:
- warnings.warn(self.message, RuntimeWarning, 2)
- if not self.attrib:
- #warn only one time!
- object.__setattr__(self, "attrib", 1)
- #@-node:__setattr__
- #@-others
-#@-node:class _MeProxyWarn
-#@-node:Proxies for self referencing
-#@+node:Task instrumentation
-#@+doc
-# This section contains code for byte code instrumenting
-# the task functions
-#@-doc
-#@nonl
-#@+node:_int_to_arg
-def _int_to_arg(value):
- return value % 256, value / 256
-#@-node:_int_to_arg
-#@+node:_correct_labels
-def _correct_labels(old_code, new_code):
- #@ << localize dot variables >>
- #@+node:<< localize dot variables >>
- hasjrel = opcode.hasjrel
- hasjabs = opcode.hasjabs
- HAVE_ARGUMENT = opcode.HAVE_ARGUMENT
- #@nonl
- #@-node:<< localize dot variables >>
- #@nl
- #@ << loop initialization >>
- #@+node:<< loop initialization >>
- labels = {}
- old_new_map = {} # map old code offset to new code offset
- n = len(old_code)
- i = 0
- j = 0
- #@nonl
- #@-node:<< loop initialization >>
- #@nl
- while i < n:
- op = old_code[i]
- nop = new_code[j]
- old_new_map[i] = j
- i = i + 1
- j = j + 1
- if op >= HAVE_ARGUMENT:
- oparg = old_code[i] + old_code[i + 1] * 256
- i = i + 2
- j = j + 2
- if nop != op:
- j += 3 # skip the 3 addition opcodes for attrib access
- else:
- #@ << add label if necessary >>
- #@+node:<< add label if necessary >>
- label = -1
- if op in hasjrel:
- label = i + oparg
- elif op in hasjabs:
- label = oparg
- if label >= 0:
- labels[i] = label
- #@nonl
- #@-node:<< add label if necessary >>
- #@nl
-
- for offset, label in labels.iteritems():
- new_offset = old_new_map[offset]
- new_label = old_new_map[label]
- op = new_code[new_offset - 3]
- #change jump arguments
- if op in hasjrel:
- jump = _int_to_arg(new_label - new_offset)
- new_code[new_offset - 2:new_offset] = jump
- elif op in hasjabs:
- new_code[new_offset - 2:new_offset] = _int_to_arg(new_label)
-#@nonl
-#@-node:_correct_labels
-#@+node:_instrument
-def _instrument(func):
- #@ << localize dot variables >>
- #@+node:<< localize dot variables >>
- opname = opcode.opname
- opmap = opcode.opmap
- jumps = opcode.hasjrel + opcode.hasjabs
- HAVE_ARGUMENT = opcode.HAVE_ARGUMENT
- co = func.func_code
- local_names = co.co_varnames
- all_names = list(co.co_names)
- global_names = set()
- #@-node:<< localize dot variables >>
- #@nl
- #@ << define local functions list_to_dict and is_local >>
- #@+node:<< define local functions list_to_dict and is_local >>
- def list_to_dict(l):
- return dict([(t[1], t[0]) for t in enumerate(l)])
-
- def is_local(name):
- return name[0] == "_" and name != "__constraint__"
- #@nonl
- #@-node:<< define local functions list_to_dict and is_local >>
- #@nl
-
- #convert code
- #@ << loop initialization >>
- #@+node:<< loop initialization >>
- # all_name_map maps names to the all_names index
- # (same like all_names.index())
- all_name_map = list_to_dict(all_names)
- if not all_name_map.has_key("me"):
- all_name_map["me"] = len(all_names)
- all_names.append("me")
-
- #
- for ln in local_names:
- if not all_name_map.has_key(ln):
- all_name_map[ln] = len(all_names)
- all_names.append(ln)
- #
-
- new_local_names = filter(is_local, local_names)
- new_local_name_map = list_to_dict(new_local_names)
-
- me_arg = _int_to_arg(all_name_map["me"])
- old_lnotab = map(ord, co.co_lnotab)
- new_lnotab = []
- tab_pos = 0
- try:
- next_tab_point = old_lnotab[0]
- except IndexError:
- next_tab_point = None
-
- last_tab_point = 0
- code = map(ord, co.co_code)
- new_code = []
- has_labels = False
- n = len(code)
- i = 0
- #@nonl
- #@-node:<< loop initialization >>
- #@nl
- while i < n:
- if i == next_tab_point:
- #@ << calculate new tab point >>
- #@+node:<< calculate new tab point >>
- increment = len(new_code) - last_tab_point
- new_lnotab.extend((increment, old_lnotab[tab_pos + 1]))
- tab_pos += 2
- try:
- next_tab_point = i + old_lnotab[tab_pos]
- last_tab_point = len(new_code)
- except IndexError:
- next_tab_point = -1
- #@nonl
- #@-node:<< calculate new tab point >>
- #@nl
-
- op = code[i]
- i += 1
- if op >= HAVE_ARGUMENT:
- #@ << calculate argument >>
- #@+node:<< calculate argument >>
- arg0 = code[i]
- arg1 = code[i+1]
- oparg = arg0 + arg1 * 256
- #@nonl
- #@-node:<< calculate argument >>
- #@nl
- i += 2
-
- if opname[op] == "LOAD_GLOBAL":
- global_names.add(oparg)
-
- elif opname[op] == "STORE_FAST":
- #@ << change "store fast" to "store attribute" >>
- #@+node:<< change "store fast" to "store attribute" >>
- name = local_names[oparg]
- if not is_local(name):
- new_code.append(opmap["LOAD_GLOBAL"])
- new_code.extend(me_arg)
- op = opmap["STORE_ATTR"]
- arg0, arg1 = _int_to_arg(all_name_map[name])
- else:
- arg0, arg1 = _int_to_arg(new_local_name_map[name])
- #@nonl
- #@-node:<< change "store fast" to "store attribute" >>
- #@nl
-
- elif opname[op] == "LOAD_FAST":
- #@ << change "load fast" to "load attribute" >>
- #@+node:<< change "load fast" to "load attribute" >>
- name = local_names[oparg]
- if not is_local(name):
- new_code.append(opmap["LOAD_GLOBAL"])
- new_code.extend(me_arg)
- op = opmap["LOAD_ATTR"]
- arg0, arg1 = _int_to_arg(all_name_map[name])
- else:
- arg0, arg1 = _int_to_arg(new_local_name_map[name])
- #@nonl
- #@-node:<< change "load fast" to "load attribute" >>
- #@nl
-
- elif op in jumps:
- has_labels = True
-
- new_code.extend((op, arg0, arg1))
- else:
- new_code.append(op)
-
- if has_labels:
- _correct_labels(code, new_code)
-
- #@ << create new code and function objects and return >>
- #@+node:<< create new code and function objects and return >>
- new_code = "".join(map(chr, new_code))
- new_lnotab = "".join(map(chr, new_lnotab))
- new_co = new.code(co.co_argcount,
- len(new_local_names),
- max(co.co_stacksize, 2),
- co.co_flags,
- new_code,
- co.co_consts,
- tuple(all_names),
- tuple(new_local_names),
- co.co_filename,
- co.co_name,
- co.co_firstlineno,
- new_lnotab,
- co.co_freevars,
- co.co_cellvars)
-
-
- func = new.function(new_co,
- func.func_globals,
- func.func_name,
- func.func_defaults,
- func.func_closure)
- func.global_names = tuple([all_names[index] for index in global_names])
- return func
- #@nonl
- #@-node:<< create new code and function objects and return >>
- #@nl
-#@nonl
-#@-node:_instrument
-#@-node:Task instrumentation
-#@+node:Wrappers
-#@+node:class _Path
-class _Path(object):
- """
- This class represents an instrumented path, to
- a task. If it points to an attribute of a task, it
- not only returns the value of the attribute. You can also
- find out the source attribute (task and attribute name)
- of the value.
- """
- #@ @+others
- #@+node:__init__
- def __init__(self, task, path_str):
- self._task = task
- self._path_str = path_str
- #@-node:__init__
- #@+node:__getattr__
- def __getattr__(self, name):
- new = getattr(self._task, name)
- if isinstance(new, Task):
- return _Path(new, self._path_str + "." + name)
-
- return _ValueWrapper(new, [(self._task, name)])
- #@-node:__getattr__
- #@+node:__str__
- def __str__(self):
- return self._path_str
- #@-node:__str__
- #@+node:__iter__
- def __iter__(self):
- return iter(self._task)
- #@nonl
- #@-node:__iter__
- #@-others
-
-#@-node:class _Path
-#@+node:_val
-#helper functions for _ValueWrapper
-#----------------------------------
-
-def _val(val):
- if isinstance(val, _ValueWrapper):
- return val._value
-
- return val
-#@-node:_val
-#@+node:_ref
-def _ref(val):
- if isinstance(val, _ValueWrapper):
- return val._ref
-
- return []
-#@-node:_ref
-#@+node:_sref
-def _sref(val, ref):
- if isinstance(val, _ValueWrapper):
- val._ref = ref
-#@nonl
-#@-node:_sref
-#@+node:_refsum
-def _refsum(refs):
- return reduce(lambda a, b: a + b, refs, [])
-#@nonl
-#@-node:_refsum
-#@+node:class _ValueWrapper
-
-
-class _ValueWrapper(object):
- """
- This class represents a value, of a task attribute or
- a return value of a task method. It contains the value,
- and the vendor of that value
- """
- #@ @+others
- #@+node:__init__
- def __init__(self, value, ref):
- self._value = value
- self._ref = ref
- #@-node:__init__
- #@+node:unicode
- def unicode(self, *args):
- if isinstance(self._value, str):
- return unicode(self._value, *args)
-
- return unicode(self._value)
- #@nonl
- #@-node:unicode
- #@+node:_vw
- def _vw(self, operand, *args):
- refs = _refsum(map(_ref, args))
- vals = map(_val, args)
- result = operand(*vals)
- return self.__class__(result, refs)
- #@-node:_vw
- #@+node:_cmp
- def _cmp(self, operand, *args):
- refs = _refsum(map(_ref, args))
- vals = map(_val, args)
- result = operand(*vals)
- map(lambda a: _sref(a, refs), args)
- return result
- #@-node:_cmp
- #@+node:__getattr__
- def __getattr__(self, name):
- return getattr(self._value, name)
- #@-node:__getattr__
- #@+node:__getitem__
- def __getitem__(self, slice):
- return self.__class__(self._value[slice], self._ref)
- #@nonl
- #@-node:__getitem__
- #@+node:__str__
- def __str__(self): return str(self._value)
- #@-node:__str__
- #@+node:__unicode__
- def __unicode__(self): return unicode(self._value)
- #@nonl
- #@-node:__unicode__
- #@+node:__repr__
- def __repr__(self): return repr(self._value)
- #@-node:__repr__
- #@+node:__nonzero__
- def __nonzero__(self): return bool(self._value)
- #@-node:__nonzero__
- #@+node:__lt__
- def __lt__(self, other): return self._cmp(op.lt, self, other)
- #@-node:__lt__
- #@+node:__le__
- def __le__(self, other): return self._cmp(op.le, self, other)
- #@-node:__le__
- #@+node:__eq__
- def __eq__(self, other): return self._cmp(op.eq, self, other)
- #@-node:__eq__
- #@+node:__ne__
- def __ne__(self, other): return self._cmp(op.ne, self, other)
- #@-node:__ne__
- #@+node:__gt__
- def __gt__(self, other): return self._cmp(op.gt, self, other)
- #@-node:__gt__
- #@+node:__ge__
- def __ge__(self, other): return self._cmp(op.ge, self, other)
- #@-node:__ge__
- #@+node:__add__
- def __add__(self, other): return self._vw(op.add, self, other)
- #@nonl
- #@-node:__add__
- #@+node:__sub__
- def __sub__(self, other): return self._vw(op.sub, self, other)
- #@-node:__sub__
- #@+node:__mul__
- def __mul__(self, other): return self._vw(op.mul, self, other)
- #@-node:__mul__
- #@+node:__floordiv__
- def __floordiv__(self, other): return self._vw(op.floordiv, self, other)
- #@-node:__floordiv__
- #@+node:__mod__
- def __mod__(self, other): return self._vw(op.mod, self, other)
- #@-node:__mod__
- #@+node:__divmod__
- def __divmod__(self, other): return self._vw(op.divmod, self, other)
- #@-node:__divmod__
- #@+node:__pow__
- def __pow__(self, other): return self._vw(op.pow, self, other)
- #@-node:__pow__
- #@+node:__lshift__
- def __lshift__(self, other): return self._vw(op.lshift, self, other)
- #@-node:__lshift__
- #@+node:__rshift__
- def __rshift__(self, other): return self._vw(op.rshift, self, other)
- #@-node:__rshift__
- #@+node:__and__
- def __and__(self, other): return self._vw(op.and_, self, other)
- #@-node:__and__
- #@+node:__xor__
- def __xor__(self, other): return self._vw(op.xor, self, other)
- #@-node:__xor__
- #@+node:__or__
- def __or__(self, other): return self._vw(op.or_, self, other)
- #@-node:__or__
- #@+node:__div__
- def __div__(self, other): return self._vw(op.div, self, other)
- #@-node:__div__
- #@+node:__radd__
- def __radd__(self, other): return self._vw(op.add, other, self)
- #@-node:__radd__
- #@+node:__rsub__
- def __rsub__(self, other): return self._vw(op.sub, other, self)
- #@-node:__rsub__
- #@+node:__rmul__
- def __rmul__(self, other): return self._vw(op.mul, other, self)
- #@-node:__rmul__
- #@+node:__rdiv__
- def __rdiv__(self, other): return self._vw(op.div, other, self)
- #@-node:__rdiv__
- #@+node:__rtruediv__
- def __rtruediv__(self, other): return self._vw(op.truediv, other, self)
- #@-node:__rtruediv__
- #@+node:__rfloordiv__
- def __rfloordiv__(self, other): return self._vw(op.floordiv, other, self)
- #@-node:__rfloordiv__
- #@+node:__rmod__
- def __rmod__(self, other): return self._vw(op.mod, other, self)
- #@-node:__rmod__
- #@+node:__rdivmod__
- def __rdivmod__(self, other): return self._vw(op.divmod, other, self)
- #@-node:__rdivmod__
- #@+node:__rpow__
- def __rpow__(self, other): return self._vw(op.pow, other, self)
- #@-node:__rpow__
- #@+node:__rlshift__
- def __rlshift__(self, other): return self._vw(op.lshift, other, self)
- #@-node:__rlshift__
- #@+node:__rrshift__
- def __rrshift__(self, other): return self._vw(op.rshift, other, self)
- #@-node:__rrshift__
- #@+node:__rand__
- def __rand__(self, other): return self._vw(op.and_, other, self)
- #@-node:__rand__
- #@+node:__rxor__
- def __rxor__(self, other): return self._vw(op.xor, other, self)
- #@-node:__rxor__
- #@+node:__ror__
- def __ror__(self, other): return self._vw(op.or_, other, self)
- #@-node:__ror__
- #@+node:__int__
- def __int__(self): return int(self._value)
- #@-node:__int__
- #@+node:__long__
- def __long__(self): return long(self._value)
- #@-node:__long__
- #@+node:__float__
- def __float__(self): return float(self._value)
- #@-node:__float__
- #@+node:__len__
- def __len__(self): return len(self._value)
- #@-node:__len__
- #@+node:__iter__
- def __iter__(self): return iter(self._value)
- #@-node:__iter__
- #@+node:__hash__
- def __hash__(self): return hash(self._value)
- #@-node:__hash__
- #@-others
-#@-node:class _ValueWrapper
-#@-node:Wrappers
-#@+node:Utilities
-#@+node:class _NEVER_USED_
-class _NEVER_USED_:
- pass
-
-#@-node:class _NEVER_USED_
-#@+node:class _StringConverter
-class _StringConverter(object):
- """This class is a helper for the to_string mechanism
- of tasks"""
- #@ @+others
- #@+node:__init__
- def __init__(self, source, format=None):
- self.source = source
- self.format = format
- #@-node:__init__
- #@+node:__getitem__
- def __getitem__(self, format):
- return _StringConverter(self.source, format)
- #@-node:__getitem__
- #@+node:__getattr__
- def __getattr__(self, name):
- class StrWrapper(object):
- def __init__(self, value, name, source, format):
- self._value = value
- self.name = name
- self.source = source
- self.format = format
-
- def __call__(self, arg):
- formatter = self.source.formatter(self.name,
- arg,
- self.format)
- return formatter(self._value(arg))
-
- value = getattr(self.source, name)
- if callable(value):
- #for methods the wrapper has to
- return StrWrapper(value, name, self.source, self.format)
-
- formatter = self.source.formatter(name, format=self.format)
- return formatter(value)
- #@-node:__getattr__
- #@-others
-#@-node:class _StringConverter
-#@+node:Multi
-def Multi(val, **kwargs):
- """returns a directory for mutlivalued attributes"""
- return dict(_default=val, **kwargs)
-#@nonl
-#@-node:Multi
-#@+node:create_relative_path
-def create_relative_path(from_, to_):
- """
- creates a relative path from absolute path
- from_ to absolute path to_
- """
- from_ = from_.split(".")
- to_ = to_.split(".")
-
- for i, parts in enumerate(zip(from_, to_)):
- from_part, to_part = parts
- if from_part != to_part:
- break
-
- from_ = from_[i:]
- to_ = to_[i:]
- return "up." * len(from_) + ".".join(to_)
-#@nonl
-#@-node:create_relative_path
-#@+node:create_absolute_path
-def create_absolute_path(from_, to_):
- """
- creates a absolute path from absolute path
- from_ to relative path to_
- """
- from_ = from_.split(".")
- to_ = to_.split(".")
-
- for i, part in enumerate(to_):
- if part != "up":
- break
-
- from_ = from_[:-i]
- to_ = to_[i:]
- return "%s.%s" % (".".join(from_), ".".join(to_))
-
-
-#@-node:create_absolute_path
-#@+node:_split_path
-def _split_path(path):
- try:
- index = path.rindex(".")
- return path[:index], path[index + 1:]
- except:
- return path
-#@-node:_split_path
-#@+node:_to_datetime
-_to_datetime = pcalendar.to_datetime
-#@nonl
-#@-node:_to_datetime
-#@+node:_get_tasks_of_sources
-def _get_tasks_of_sources(task, attrib_filter="end,start,effort,length,duration"):
- #return all source tasks, this task is dependend on
-
- dep_tasks = {}
-
- while task:
- for dep in task._sources.values():
- for d in dep:
- path, attrib = _split_path(d)
- if attrib and attrib_filter.find(attrib) >= 0:
- dep_tasks[path] = True
-
- task = task.up
-
- return dep_tasks.keys()
-#@-node:_get_tasks_of_sources
-#@+node:_build_balancing_list
-def _build_balancing_list(tasks):
- """
- Returns a specialy sorted list of tasks.
- If the tasks will allocate resources in the sorting order of that list
- correct balancing is ensured
- """
-
- # first sort the list for attributes
- index = 0
- balancing_list = [(-t.priority, t.balance, index, t) for index, t in enumerate(tasks)]
- balancing_list.sort()
-
- #print
- #for p, b, i, t in balancing_list:
- # print p, b, i, t.path
-
- balancing_list = [ t for p, b, i, t in balancing_list ]
-
- #now correct the presorted list:
- #if task a is dependent on task b, b will be moved before a
-
- done_map = { }
- count = len(balancing_list)
- while len(done_map) < count:
- for i in range(count):
- to_inspect = balancing_list[i]
- if done_map.has_key(to_inspect):
- continue
-
- done_map[to_inspect] = True
- break
- else:
- break
-
- #@ << define inspect_depends_on >>
- #@+node:<< define inspect_depends_on >>
- inspect_path = to_inspect.path + "."
- sources = _get_tasks_of_sources(to_inspect)
- sources = [ s + "." for s in sources
- if not inspect_path.startswith(s) ]
-
- # the if in the later line ignores assignments like
- # like start = up.start (i.e. references to parents)
- # this will be handled in the second if of inspect_depends_on
- # and can cause errors otherwise
-
- def inspect_depends_on(task):
- cmp_path = task.path + "."
- for src in sources:
- if cmp_path.startswith(src):
- #task is a source of to_inspect
- return True
-
- if inspect_path.startswith(cmp_path):
- #to_inspect is a child of task
- return True
-
- return False
- #@nonl
- #@-node:<< define inspect_depends_on >>
- #@nl
- for j in range(i + 1, count):
- check_task = balancing_list[j]
- if done_map.has_key(check_task):
- continue
-
- if inspect_depends_on(check_task):
- del balancing_list[j]
- balancing_list.insert(i, check_task)
- i += 1 # to_inspect is now at i + 1
-
-
- return balancing_list
-#@-node:_build_balancing_list
-#@+node:_as_string
-def _as_string(val):
- if isinstance(val, basestring):
- return '"""%s"""' % val.replace("\n", "\\n")
-
- if isinstance(val, pcalendar._WorkingDateBase):
- return '"%s"' % val.strftime("%Y-%m-%d %H:%M")
-
- if isinstance(val, datetime.datetime):
- return '"%s"' % val.strftime("%Y-%m-%d %H:%M")
-
- if isinstance(val, datetime.timedelta):
- return '"%id %iM"' % (val.days, val.seconds / 60)
-
- if isinstance(val, tuple):
- result = map(_as_string, val)
- return "(%s)" % ", ".join(result)
-
- if isinstance(val, list):
- result = map(_as_string, val)
- return "[%s]" % ", ".join(result)
-
- if isinstance(val, resource.Resource):
- return val._as_string()
-
- if isinstance(val, Task):
- return val.path
-
- return str(val)
-#@-node:_as_string
-#@+node:_step_tasks
-def _step_tasks(task):
- if isinstance(task, Task):
- yield task
-
- stack = [iter(task.children)]
- while stack:
- for task in stack[-1]:
- yield task
-
- if task.children:
- stack.append(iter(task.children))
- break
- else:
- stack.pop()
-#@-node:_step_tasks
-#@-node:Utilities
-#@+node:Cache
-instrumentation_cache = {}
-balancing_cache = {}
-
-def clear_cache():
- instrumentation_cache.clear()
- balancing_cache.clear()
-#@nonl
-#@-node:Cache
-#@+node:Resource Allocators
-#@+others
-#@+node:VariableLoad
-def VariableLoad(limit=0):
- """
- Allocates the resource with maximal possible load.
- If limit is given, a the load is at least limit or more.
- """
- try:
- balance = me.balance
- except NameError:
- balance = SLOPPY
-
- if balance != SLOPPY:
- raise RuntimeError("You may specify variable_load only with balance=SLOPPY")
-
- return -limit
-#@-node:VariableLoad
-#@+node:_calc_load
-def _calc_load(task, resource):
- #changed at the resource instance
- load = resource.__dict__.get("load")
- if load is not None: return load
-
- load = task.__dict__.get("load")
- if load is not None: return load
-
- #inherited by the task
- return min(task.load, task.max_load, resource.max_load or 100.0)
-#@-node:_calc_load
-#@+node:_calc_maxload
-def _calc_maxload(task, resource):
- #changed at the resource instance
- max_load = resource.__dict__.get("max_load")
- if max_load: return max_load
-
- #an explicit load can overwrite max_load
- load = max(resource.__dict__.get("load", 0),
- task.__dict__.get("load"), 0)
-
- #change at the task
- max_load = task.__dict__.get("max_load")
- if max_load: return max(max_load, load)
-
- #inherited by the resource
- max_load = resource.max_load
- if max_load: return max(max_load, load)
-
- #inherited by the task
- return max(task.max_load, load)
-#@-node:_calc_maxload
-#@+node:class AllocationAlgorithm
-class AllocationAlgorithm(object):
- """This class is a base for resource allocation algorithms"""
- #@ @+others
- #@+node:test_allocation
- def test_allocation(self, task, resource):
- """This method simulates the allocation of a specific resource.
- It returns a list of values representing the state of the allocation.
- The task allocator calls test_allocation for every alternative resource.
- It compares the first items of all return lists, and allocates the
- resource with the minum first item value"""
- return (task.end, )
- #@-node:test_allocation
- #@+node:allocate
- def allocate(self, task, state):
- """This method eventually allocates a specific resource.
- State is the return list of test_allocation"""
- pass
- #@-node:allocate
- #@-others
-#@-node:class AllocationAlgorithm
-#@+node:class StrictAllocator
-class StrictAllocator(AllocationAlgorithm):
- """This class implements the STRICT resource allocation"""
- #@ @+others
- #@+node:_distribute_len_loads
- def _distribute_len_loads(self, task, resource, effort, length):
- # A special load calculation, if effort and length are given.
- # and the resources have a defined maxload, the load must be
- # individually calculated for each resource.
-
- # Formulars: r=resources, t=task
- # effort = length * efficiency(t) * sum[load(r) * effiency(r)]
- # ==> sum_load = sum[load(r) * effiency(r)]
- # = effort / (length * efficiency(t))
- #
-
- sum_load = float(effort) / (task.efficiency * length)
-
- # algorithm:
- # The goal is to distribute the load (norm_load) equally
- # to all resources. If a resource has a max_load(r) < norm_load
- # the load of this resource will be max_load(r), and the other
- # resources will have another (higher) norm_load
-
- max_loads = map(lambda r: (_calc_maxload(task, r), r), resource)
- max_loads.sort()
-
- efficiency_sum = sum(map(lambda r: r.efficiency, resource))
- norm_load = sum_load / efficiency_sum
-
- loads = {}
- for max_load, r in max_loads[:-1]:
- if max_load < norm_load:
- loads[r] = max_load
- efficiency_sum -= r.efficiency
- sum_load -= max_load * r.efficiency
- norm_load = sum_load / efficiency_sum
- else:
- loads[r] = norm_load
-
- max_load, r = max_loads[-1]
- loads[r] = norm_load
- return loads
- #@-node:_distribute_len_loads
- #@+node:test_allocation
- def test_allocation(self, task, resource):
- effort = task.__dict__.get("effort")
- to_start = task._to_start
- to_end = task._to_end
- to_delta = task._to_delta
-
- if task.performed_end:
- start = to_start(max(task.performed_end,
- task.root.calendar.now,
- task.start))
- else:
- start = task.start
- if task.root.has_actual_data and task.complete == 0:
- start = max(start, to_start(task.root.calendar.now))
-
- base_start = to_start(task.performed_start or task.start)
- calc_load = lambda r: _calc_load(task, r)
- loads = map(lambda r: (r, calc_load(r)), resource)
-
- length = task.__dict__.get("length")
- duration = task.__dict__.get("duration")
- end = task.__dict__.get("end")
-
- #@ << correct length >>
- #@+node:<< correct length >>
- if length is not None:
- length = to_delta(max(length - (task.start - base_start), 0))
- #@nonl
- #@-node:<< correct length >>
- #@nl
- #@ << correct duration >>
- #@+node:<< correct duration >>
- if duration is not None:
- delta = task.start.to_datetime() - base_start.to_datetime()
- delta = to_delta(delta, True)
- duration = to_delta(max(duration - delta, 0), True)
- #@nonl
- #@-node:<< correct duration >>
- #@nl
- #@ << check end >>
- #@+node:<< check end >>
- if end is not None:
- length = end - start
- if length <= 0: return False
- #@nonl
- #@-node:<< check end >>
- #@nl
- #@ << correct effort and (re)calculate length >>
- #@+node:<< correct effort and (re)calculate length >>
- if effort is not None:
- effort -= task.performed_effort
- effort = to_delta(max(effort, 0))
- if effort <= 0: return False
-
- if length is not None:
- #if length and effort is set, the load will be calculated
- length = length or task.calendar.minimum_time_unit
- loads = self._distribute_len_loads(task, resource,
- effort, length)
- def calc_load(res):
- return loads[res]
- else:
- #the length depends on the count of resources
- factor = sum(map(lambda a: a[0].efficiency * a[1],
- loads)) * task.efficiency
- length = effort / factor
- #@nonl
- #@-node:<< correct effort and (re)calculate length >>
- #@nl
- #@ << set adjust_date and delta >>
- #@+node:<< set adjust_date and delta >>
- if length is not None:
- adjust_date = lambda date: date
- delta = to_delta(length).round()
- else:
- assert(duration is not None)
- adjust_date = _to_datetime
- delta = datetime.timedelta(minutes=duration)
- #@nonl
- #@-node:<< set adjust_date and delta >>
- #@nl
-
- # find the earliest start date
- start, book_load\
- = self.balance(task, start, delta, adjust_date,
- calc_load, resource)
-
- end = to_end(start + delta)
- start = to_start(start)
-
- if effort is None:
- #length is frozen ==> a new effort will be calculated
- factor = sum(map(lambda a: a[1], loads))
- length = end - start
-
- effort = to_delta(length * factor\
- + task.performed_effort).round()
-
- return (end, book_load), resource, calc_load, start, effort
- #@-node:test_allocation
- #@+node:allocate
- def allocate(self, task, state):
- # now really book the resource
- end_bl, resource, calc_load, start, effort = state
- end = end_bl[0]
- cal = task.root.calendar
- to_start = task._to_start
- to_end = task._to_end
- to_delta = task._to_delta
-
- task.start = task.performed_start \
- and to_start(task.performed_start) \
- or to_start(start)
-
- task.end = end
- task._unfreeze("length")
- task._unfreeze("duration")
- length = end - start
-
- for r in resource:
- book_load = calc_load(r)
- work_time = to_delta(length * book_load).round()
- r.book_task(task, start, end, book_load, work_time, False)
-
- #the following lines are important to be exactly at this
- #positions in that order:
- # done and todo are dependend on:
- # - the existence of effort (if effort was set or not set)
- # - book_task (they can only be calculated, if the task is booked)
- # - booked_resource (to get the booked tasks)
- task.booked_resource = resource
- task.done = task.done
- task.todo = task.todo
- task.length = end - task.start
- task.effort = to_delta(effort + task.performed_effort)
- #@-node:allocate
- #@+node:balance
- #now effort exists always
-
-
- def balance(self, task, start, delta, adjust_date,
- calc_load, resource):
- book_load = max(map(lambda r: r.get_load(task.start, task.scenario), resource))
- return start, book_load
- #@-node:balance
- #@-others
-#@-node:class StrictAllocator
-#@+node:class SmartAllocator
-
-
-class SmartAllocator(StrictAllocator):
- #@ @+others
- #@+node:balance
- def balance(self, task, start, delta, adjust_date,
- calc_load, resource):
- #find the earliest start date, at which all
- #resources in the team are free
-
- cal = task.root.calendar
- to_start = task._to_start
- start = adjust_date(start)
- scenario = task.scenario
-
- while True:
- #we have finished, when all resources have the
- #same next free start date
- for r in resource:
- max_load = _calc_maxload(task, r)
- load = calc_load(r)
-
- #find the next free time of the resource
- s = r.find_free_time(start, delta, load, max_load, scenario)
- if s != start:
- s = to_start(s)
- start = adjust_date(s)
- break
- else:
- #only one resource
- break
-
- return start, 1.0
- #@-node:balance
- #@-others
-#@-node:class SmartAllocator
-#@+node:class SloppyAllocator
-
-
-class SloppyAllocator(AllocationAlgorithm):
- #@ @+others
- #@+node:test_allocation
- def test_allocation(self, task, resource):
- if task.__dict__.has_key("effort"):
- return self.test_allocation_effort(task, resource)
-
- return self.test_allocation_length(task, resource)
- #@-node:test_allocation
- #@+node:test_allocation_length
- def test_allocation_length(self, task, resource):
- #length is frozen ==> effort will be calculated
- to_start = task._to_start
- to_end = task._to_end
- to_delta = task._to_delta
-
- end = task.end
- if task.performed_end:
- start = to_start(max(task.performed_end,
- task.root.calendar.now,
- start))
- else:
- start = task.start
-
- base_start = to_start(task.performed_start or task.start)
- length = to_delta(max(task.length - (start - base_start), 0))
- sum_effort = 0
- intervals = []
- scenario = task.scenario
- for r in resource:
- date = start
- max_load = _calc_maxload(task, r)
- book_load = _calc_load(task, r)
-
- while date < end:
- #find free time intervals and add them for booking
- endi, load = r.end_of_booking_interval(date, task)
- endi = min(endi, end)
- endi = to_end(endi)
-
- if book_load <= 0:
- #variable book_load ==> calc the maxmimal possible book_load >= (the given book_load)
- used_book_load = - book_load
- diff_load = max_load - load
- if diff_load and diff_load >= book_load:
- used_book_load = diff_load
- else:
- used_book_load = max_load
- else:
- used_book_load = book_load
-
- if max_load - load >= used_book_load:
- intervals.append((r, used_book_load, date, endi))
- sum_effort = (endi - date) * used_book_load
-
- date = to_start(endi)
-
- return -sum_effort, end, resource, intervals
- #@-node:test_allocation_length
- #@+node:test_allocation_effort
- def test_allocation_effort(self, task, resource):
- #effort is frozen ==> length will be calculated
-
- to_start = task._to_start
- to_end = task._to_end
- to_delta = task._to_delta
-
- intervals = []
- effort = task.__dict__.get("effort")
-
- if task.performed_end:
- next_date = to_start(max(task.performed_end,
- task.root.calendar.now,
- task.start))
- else:
- next_date = task.start
- if task.root.has_actual_data and task.complete == 0:
- next_date = max(next_date, to_start(task.root.calendar.now))
-
- #walks chronologicly through the booking
- #intervals of each resource, and reduces
- #the effort for each free interval
- #until it becomes 0
-
- alloc_effort = effort
- effort -= task.performed_effort
- while effort > 0:
- date = next_date
-
- interval_resource = []
- interval_end = to_start(sys.maxint)
- factor = 0
-
- for r in resource:
- max_load = _calc_maxload(task, r)
- book_load = _calc_load(task, r)
- end, load = r.end_of_booking_interval(date, task)
- interval_end = to_start(min(end, interval_end))
-
- if book_load <= 0:
- #variable book_load ==> calc the maxmimal possible book_load >= (the given book_load)
- book_load = - book_load
- diff_load = max_load - load
- if diff_load and diff_load >= book_load:
- book_load = diff_load
- else:
- book_load = max_load
-
- if book_load + load <= max_load:
- resource_factor = book_load * r.efficiency
- interval_resource.append((r, book_load, resource_factor))
- factor += resource_factor
-
-
-
- next_date = interval_end
- if factor:
- factor *= task.efficiency
- length = to_delta(effort / factor).round()
- end = date + length
-
- if interval_end >= end:
- next_date = interval_end = end
- effort = 0
- book_end = end
- else:
- book_end = interval_end
- length = book_end - date
- minus_effort = length * factor
- effort -= minus_effort
-
- book_end = to_end(book_end)
- intervals.append((date, book_end, length, interval_resource))
-
- return next_date, alloc_effort, resource, intervals
- #@-node:test_allocation_effort
- #@+node:allocate
- def allocate(self, task, state):
- if task.__dict__.has_key("effort"): self.allocate_effort(task, state)
- else: self.allocate_length(task, state)
- #@-node:allocate
- #@+node:allocate_length
- def allocate_length(self, task, state):
- # now really book the resource
- neg_sum_effort, end, resource, intervals = state
-
- cal = task.root.calendar
- to_start = task._to_start
- to_end = task._to_end
- to_delta = task._to_delta
-
- task.start = to_start(task.performed_start or task.start)
- task.end = to_end(end)
- task._unfreeze("length")
- task._unfreeze("duration")
-
- effort = 0
- for r, load, s, e in intervals:
- work_time = to_delta((e - s) * load).round()
- effort += work_time
- r.book_task(task, s, e, load, work_time, False)
-
- #see comment at StrictAllocator.allocate
- task.booked_resource = resource
- task.done = task.done
- task.todo = task.todo
- task.effort = to_delta(effort + task.performed_effort).round()
- #@-node:allocate_length
- #@+node:allocate_effort
- def allocate_effort(self, task, state):
- # now really book the resource
- end, effort, resource, intervals = state
- to_start = task._to_start
- to_end = task._to_end
- to_delta = task._to_delta
-
- task.start = task.performed_start \
- and to_start(task.performed_start) \
- or to_start(intervals[0][0])
- task.end = to_end(end)
- task._unfreeze("length")
- task._unfreeze("duration")
-
- for start, end, length, resources in intervals:
- for r, load, factor in resources:
- work_time = to_delta(length * load)
- r.book_task(task, start, end, load, work_time, False)
-
- task.booked_resource = resource
- task.done = task.done
- task.todo = task.todo
- task.effort = to_delta(effort)
- task.length = task.end - task.start
- #@-node:allocate_effort
- #@-others
-#@-node:class SloppyAllocator
-#@-others
-
-_smart_allocator = SmartAllocator()
-_sloppy_allocator = SloppyAllocator()
-_strict_allocator = StrictAllocator()
-_allocators = { SMART: _smart_allocator,
- SLOPPY: _sloppy_allocator,
- STRICT: _strict_allocator }
-
-_allocator_strings = { SMART: "SMART",
- SLOPPY: "SLOPPY",
- STRICT: "STRICT" }
-#@-node:Resource Allocators
-#@+node:Load Calculators
-#@+node:YearlyMax
-def YearlyMax(value):
- """
- Calculates a load parameter with a maximal yearly workload
- """
- #@ << calculate calendar and time_diff >>
- #@+node:<< calculate calendar and time_diff >>
- try:
- cal = me.calendar
- except NameError:
- cal = pcalendar._default_calendar
-
- time_diff = cal.Minutes(value)
- #@nonl
- #@-node:<< calculate calendar and time_diff >>
- #@nl
- return float(time_diff) / \
- (cal.working_days_per_year \
- * cal.working_hours_per_day \
- * 60)
-#@nonl
-#@-node:YearlyMax
-#@+node:WeeklyMax
-def WeeklyMax(value):
- """
- Calculates a load parameter with a maximal weekly workload
- """
- #@ << calculate calendar and time_diff >>
- #@+node:<< calculate calendar and time_diff >>
- try:
- cal = me.calendar
- except NameError:
- cal = pcalendar._default_calendar
-
- time_diff = cal.Minutes(value)
- #@nonl
- #@-node:<< calculate calendar and time_diff >>
- #@nl
- return float(time_diff) / \
- (cal.working_days_per_week \
- * cal.working_hours_per_day \
- * 60)
-
-#@-node:WeeklyMax
-#@+node:MonthlyMax
-def MonthlyMax(value):
- """
- Calculates a load parameter with a maximal monthly workload
- """
- #@ << calculate calendar and time_diff >>
- #@+node:<< calculate calendar and time_diff >>
- try:
- cal = me.calendar
- except NameError:
- cal = pcalendar._default_calendar
-
- time_diff = cal.Minutes(value)
- #@nonl
- #@-node:<< calculate calendar and time_diff >>
- #@nl
- return float(time_diff) / \
- (cal.working_days_per_month \
- * cal.working_hours_per_day \
- * 60)
-
-#@-node:MonthlyMax
-#@+node:DailyMax
-def DailyMax(value):
- """
- Calculates a load parameter with a maximal daily workload
- """
- #@ << calculate calendar and time_diff >>
- #@+node:<< calculate calendar and time_diff >>
- try:
- cal = me.calendar
- except NameError:
- cal = pcalendar._default_calendar
-
- time_diff = cal.Minutes(value)
- #@nonl
- #@-node:<< calculate calendar and time_diff >>
- #@nl
- return float(time_diff) / (cal.working_hours_per_day * 60)
-#@-node:DailyMax
-#@-node:Load Calculators
-#@+node:Task
-#@+node:class _TaskProperty
-class _TaskProperty(object):
- #@ @+others
- #@+node:__init__
- def __init__(self, method):
- self.method = method
- #@-node:__init__
- #@+node:__get__
- def __get__(self, instance, owner):
- if not instance:
- return None
-
- return instance._wrap_attrib(self.method)
- #@-node:__get__
- #@-others
-#@-node:class _TaskProperty
-#@+node:class _RoundingTaskProperty
-class _RoundingTaskProperty(object):
- #@ @+others
- #@+node:__init__
- def __init__(self, method, name):
- self.method = method
- self.name = name
- #@-node:__init__
- #@+node:__get__
- def __get__(self, instance, owner):
- if not instance:
- return None
-
- result = instance._wrap_attrib(self.method).round()
- if instance._is_frozen:
- #correct the attrib to the rounded value
- setattr(instance, self.name, result)
-
- return result
- #@-node:__get__
- #@-others
-#@-node:class _RoundingTaskProperty
-#@+node:class Task
-class Task(object):
- #@ << description >>
- #@+node:<< description >>
- """
- This class represents a single task in the project tree. A task
- can have other child tasks, or is a leaf of the tree. Resources
- will be allocated only to leafes. You will never create task
- objects by your self, they are created indirectly by Projects.
-
- @var root:
- Returns the root project task.
-
- @var up:
- Returns the parent task.
-
- @var title:
- Specifies an alternative more descriptive name for the task.
-
- @var start:
- The start date of the task. Valid values are expressions and
- strings specifing a datatime
-
- @var end:
- The end date of the task. Valid values are expressions and
- strings.
-
- @var effort:
- Specifies the effort needed to complete the task. Valid values
- are expressions and strings. (Todo: What happens, in case of
- specified performance data...)
-
-
- @var length:
- Specifies the time the task occupies the resources. This is
- working time, not calendar time. 7d means 7 working days, not one
- week. Whether a day is considered a working day or not depends on
- the defined working hours and global vacations.
-
- @var duration:
- Specifies the time the task occupies the resources. This is
- calendar time, not working time. 7d means one week.
-
- @var buffer:
- Specifies the time a task can be delayed, without moving dependend
- milestones. A Task with a buffer S{<=} 0d is part of the critical
- chain. This attribute is readonly.
-
- @var complete:
- Specifies what percentage of the task is already completed.
-
- @var todo:
- Specifies the effort, which needs to be done to complete a
- task. This is another (indirect) way to specify the ME{complete}
- attribute.
-
- @var done:
- Specifies the work effort, which has been already done. This
- attribute is readonly.
-
- @var estimated_effort:
- Specifies the estimated_effort given by setting the effort property.
-
- @var performed:
- Specifies a list of actual working times performed on the task.
- The format is: C{[ (resource, from, to, time), ... ]}
-
- @var performed_work_time:
- Specifies the sum of all working times. This attribute is
- readonly.
-
- @var performed_effort:
- Specifies the complete effort of all working times. This attribute is
- readonly.
-
- @var performed_start:
- The start date of the performed data.
-
- @var performed_end:
- The end date of the performed data.
-
- @var performed_resource:
- The resources who have already performed on the task. This attribute is readonly.
-
-
- @var balance:
- Specifies the resource allocation type. Possible values are
- CO{STRICT}, CO{SLOPPY}, CO{SMART}.
-
- @var resource:
- Specifies the possible resources, that may be allocated for the
- task.
-
- @var booked_resource:
- Specifies the allocated resources of a task. This attribute is
- readonly.
-
- @var load:
- Specifies the daily load of a resource for an allocation of the
- specified task. A load of 1.0 (default) means the resource is
- allocated for as many hours as specified by
- ME{working_hours_per_day}. A load of 0.5 means half that many
- hours.
-
- @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 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 milestone:
- Specified if the task is a milestone. The possible values are
- C{True} or "later". If the start date of the milestone is not
- a valid working date, the milestone will appear at the previous
- working date before the given start date. If "later" is specified
- the milestone will appear at the next valid working date.
- A milestone has always an effort of 0d.
-
- @var priority:
- Specifies a priority between 1 and 1000. A task with higher
- priority is more likely to get the requested resources. The
- default priority is 500.
-
- @var children:
- Specifies a list of all subtasks. A task without children is
- called a leaf task index{leaf task} otherwise it is called a
- parent task index{parent task}. This attribute is readonly.
-
- @var depth:
- Specifies the depth of the task within the hierachy. This
- attribute is readonly.
-
- @var index:
- Specifies a structural index number. This attribute is readonly.
-
- @var path:
- Specifies the path.
-
- @var copy_src:
- Specifies the path to an other task. When you set this attribute,
- all attributes (except of ME{start} and ME{end}) of copy_src will
- be copied to the current task. This is usefull if you want to
- define the same task, in diffent project definitions. It acts like
- a task link.
-
- @var scenario:
- The scenario which is currently evaluated. This attribute is readonly.
-
- @var dont_inherit:
- A list of attribute names, which will be not inherited by
- subtasks.
-
- @var calendar:
- Specifies the task calendar.
-
- @var working_days_per_week:
- Specifies the days within a working week. This value is used
- internally to convert time differences from weeks to days. The
- default value is 5 days.
-
- @var working_days_per_month:
- Specifies the days within a working month. This value is used
- internally to convert time differences from months to days. The
- default value is 20 days.
-
- @var working_days_per_year:
- Specifies the days within a working year. This value is used
- internally to convert time differences from years to days The
- default value is 200 days.
-
- @var working_hours_per_day:
- Specifies the hours within a working day. This value is used
- internally to convert time differences from are entered in days to
- hours. The default value is 8 hours.
-
- @var minimum_time_unit:
- Specifies the minimum resolution in minutes for the task
- scheduling. The default value is 15 minutes.
-
- @var vacation:
- Specifies a public vacation for the calendar. 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.
-
- @var extra_work:
- Specifies additional worktime. 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.
-
- @var working_days:
- Specifies the weekly working time within calendar. The format of
- this attribute is: [ (day_range, time_range, ...), (day_range, time_range, ...), ... ].
- day_range is a comma sperated string of week days. Valid values
- are mon, tue, wed, thu, fri, sat, sun.
- time_range is string specifing a time interval like
- 8:00-10:00. You can specified any number of time_ranges, following
- the first.
-
- @var now:
- Specifies the current daytime and is a date literal. ME{now} is
- used to calculate several task attributes.
-
- """
- #@nonl
- #@-node:<< description >>
- #@nl
- #@ << declarations >>
- #@+node:<< declarations >>
- # Variables for the gui interface
- _date_completion = { "Date": 'Date("|")',
- "max": "max(|)",
- "min": "min(|)",
- "Multi" : "Multi(|)" }
-
-
- _delta_completion = { "Delta" : 'Delta("|")',
- "Multi" : "Multi(|)" }
-
-
- __attrib_completions__ = { \
- "def NewTask():" : "def |NewTask():\n",
- "milestone": 'milestone = True',
- "start": 'start = ',
- "end": 'end = ',
- "effort": 'effort = "|"',
- "duration": 'duration = "|"',
- "length": 'length = "|"',
- "todo": 'todo = "|"',
- "done": 'done = "|"',
- "title": 'title = "|"',
- "load": 'load = ',
- "max_load": 'max_load = ',
- "efficiency": 'efficiency = ',
- "complete": 'complete = ',
- "copy_src": 'copy_src =',
- "__constraint__": '__constraint__():\n|"',
- "priority": 'priority = ',
- "balance" : 'balance = ',
- "resource": 'resource = ',
- "performed" : 'performed = [(|resource, "2002-02-01", "2002-02-05", "2H"),]',
- "add_attrib": "add_attrib(|'name', None)",
- "working_days_per_week": 'working_days_per_week = ',
- "working_days_per_month": 'working_days_per_month = ',
- "working_days_per_year": 'working_days_per_year = ',
- "working_hours_per_day": 'working_hours_per_day = ',
- "minimum_time_unit": 'minimum_time_unit = ',
- "vacation": 'vacation = [("|2002-02-01", "2002-02-05")]',
- "extra_work": 'extra_work = [("|2002-02-01", "2002-02-05")]',
- "working_days" : 'working_days = ["|mon,tue,wed,thu,fri", "8:00-12:00", "13:00-17:00"]',
- "now": 'now = "|"',
- "calendar" : 'calendar = ',
- "#load": { "YearlyMax": 'YearlyMax("|")',
- "WeeklyMax": 'WeeklyMax("|")',
- "MonthlyMax": 'MonthlyMax("|")',
- "DailyMax": 'DailyMax("|")',
- "VariableLoad" : "VariableLoad(|)"},
- "#max_load": { "YearlyMax": 'YearlyMax("|")',
- "WeeklyMax": 'WeeklyMax("|")',
- "MonthlyMax": 'MonthlyMax("|")',
- "DailyMax": 'DailyMax("|")' },
- "#start": _date_completion,
- "#end": _date_completion,
- "#effort": _delta_completion,
- "#duration": _delta_completion,
- "#length": _delta_completion,
- "#todo": _delta_completion,
- "#done": _delta_completion,
- "#resource" : "get_resource_completions",
- "#calendar" : "get_calendar_completions",
- "#balance": { "STRICT": "STRICT",
- "SMART": "SMART",
- "SLOPPY": "SLOPPY" } }
-
-
- formats = { "start" : "%x %H:%M",
- "end" : "%x %H:%M",
- "performed_start" : "%x %H:%M",
- "performed_end" : "%x %H:%M",
- "load" : "%.2f",
- "length" : "%dd{ %HH}{ %MM}",
- "effort" : "%dd{ %HH}{ %MM}",
- "estimated_effort" : "%dd{ %HH}{ %MM}",
- "performed_effort" : "%dd{ %HH}{ %MM}",
- "duration" : "%dd{ %HH}{ %MM}",
- "complete" : "%i",
- "priority" : "%i",
- "todo" : "%dd{ %HH}{ %MM}",
- "done" : "%dd{ %HH}{ %MM}",
- "efficiency" : "%.2f",
- "buffer" : "%dd{ %HH}{ %MM}",
- "costs" : "%.2f",
- "sum" : "%.2f",
- "max" : "%.2f",
- "min" : "%.2f",
- "milestone" : "%s",
- "resource" : "%s",
- "booked_resource" : "%s",
- "performed_resource" : "%s" }
-
- _constraint = None
- _is_frozen = False
- _is_compiled = False
- _is_parent_referer = False
-
- scenario = None # only for autocompletion
- milestone = False
- performed = ()
- performed_resource = ()
- booked_resource = ()
- _performed_resource_length = ()
- _resource_length = ()
- dont_inherit = ()
- performed_start = None
- performed_end = None
- performed_work_time = pcalendar.Minutes(0)
-
- _setting_hooks = {}
- #@nonl
- #@-node:<< declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, func, name, parent=None, index=1):
-
- assert(type(func) == types.FunctionType)
-
- func_key = (func.func_code, func.func_closure and id(func.func_closure))
-
- try:
- instrumented = instrumentation_cache[func_key]
- except KeyError:
- instrumented = _instrument(func)
- instrumented.org_code = func_key
- instrumentation_cache[func_key] = instrumented
-
- func.task_func = instrumented # will be used in the gui
- self._function = instrumented
- self.name = name
- self.up = parent
- self.children = []
- self._sources = {} # all tasks, I am linked to
- self._dependencies = {} # all tasks that link to me
- self._original_values = {}
- self._properties = {} # a registry of all non standard attributes
- self.title = self.name
- self.root = parent and parent.root or self
- self.scenario = self.root.scenario
- self.path = parent and parent.path + "." + name or name
- self.depth = len(self.path.split(".")) - 1
- self.index = parent and ("%s.%i" % (parent.index, index)) \
- or str(index)
- if self.formats.has_key(name):
- raise AttributeError("Task name '%s' hides attribute of parent." \
- % name)
-
- cal = self.calendar
- self._to_delta = cal.Minutes
- self._to_start = cal.StartDate
- self._to_end = cal.EndDate
-
- #@-node:__init__
- #@+node:__iter__
- def __iter__(self):
- return _step_tasks(self)
- #@-node:__iter__
- #@+node:__repr__
- def __repr__(self):
- return "" % self.name
- #@-node:__repr__
- #@+node:__cmp__
- def __cmp__(self, other):
- try:
- return cmp(self.path, other.path)
- except Exception:
- return cmp(self.path, other)
- #@-node:__cmp__
- #@+node:__getattr__
- def __getattr__(self, name):
- try:
- if name[0] != "_":
- parent = self.up
- while parent:
- if name not in parent.dont_inherit:
- result = getattr(parent, name)
- if not (isinstance(result, Task) and result.up == parent):
- return result
-
- parent = parent.up
- except AttributeError:
- pass
- except IndexError:
- raise AttributeError()
-
- exception = AttributeError("'%s' is not a valid attribute of '%s'."
- % (name, self.path))
- exception.is_frozen = self._is_frozen
- raise exception
- #@-node:__getattr__
- #@+node:_idendity_
- def _idendity_(self): return self.root.id + self.path[4:]
- #@-node:_idendity_
- #@+node:_set_hook
- def _set_hook(cls, attrib_name, function=None):
- if function:
- cls._setting_hooks[attrib_name] = function
- else:
- try:
- del cls._setting_hooks[attrib_name]
- except KeyError: pass
-
-
- _set_hook = classmethod(_set_hook)
- #@nonl
- #@-node:_set_hook
- #@+node:Public methods
- #@+node:to_string
- def to_string(self): return _StringConverter(self)
- to_string = property(to_string)
- #@nonl
- #@-node:to_string
- #@+node:indent_name
- def indent_name(self, ident=" "):
- """
- returns a indented name, according to its depth in the hierachy.
- """
-
- return ident * self.depth + self.name
-
- indent_name.attrib_method = True
- indent_name.__call_completion__ = "indent_name()"
- #@-node:indent_name
- #@+node:costs
- def costs(self, cost_name, mode="ep"):
- """
- calculates the resource costs for the task.
- cost_name is the name of a rate attribute of the reosurce
- mode is character combination:
- e calculates the estimated costs
- p calculates the performed costs
- ==> pe calculates all costs
- """
-
- if self.children:
- return sum([ c.costs(cost_name, mode) for c in self.children])
-
- costs = 0
- if 'e' in mode:
- costs += sum(map(lambda rl: getattr(rl[0], cost_name) * rl[1],
- self._resource_length))
-
- if 'p' in mode:
- costs += sum(map(lambda rl: getattr(rl[0], cost_name) * rl[1],
- self._performed_resource_length))
-
- costs /= (60.0 * self.root.calendar.working_hours_per_day)
- return round(costs, 2)
-
- costs.attrib_method = True
- costs.__call_completion__ = 'costs("|")'
- #@-node:costs
- #@+node:sum
- def sum(self, attrib_name):
- val = 0
-
- if self.children:
- val += sum(map(lambda c: c.sum(attrib_name), self.children))
- if self.is_inherited(attrib_name):
- return val
-
- if attrib_name not in self.dont_inherit:
- return val
-
- return val + getattr(self, attrib_name)
-
- sum.attrib_method = True
- sum.__call_completion__ = 'sum("|")'
-
- #@-node:sum
- #@+node:min
- def min(self, attrib_name):
- if self.children:
- return min(map(lambda c: c.min(attrib_name), self.children))
-
- return getattr(self, attrib_name)
-
- min.attrib_method = True
- min.__call_completion__ = 'min("|")'
-
- #@-node:min
- #@+node:max
- def max(self, attrib_name):
- if self.children:
- return max(map(lambda c: c.max(attrib_name), self.children))
-
- return getattr(self, attrib_name)
-
- max.attrib_method = True
- max.__call_completion__ = 'max("|")'
-
- #@-node:max
- #@+node:all_resources
- def all_resources(self):
- result = self._all_resources_as_dict()
- result = result.keys()
- result.sort()
- return result
- #@-node:all_resources
- #@+node:get_task
- def get_task(self, path=None):
- """
- Returns a task with the given path.
- """
-
- if not path:
- return self
-
- names = path.split(".")
- rest = ".".join(names[1:])
- result = getattr(self, names[0], None)
- return isinstance(result, Task) and result.get_task(rest) or None
- #@-node:get_task
- #@+node:snapshot
- def snapshot(self, indent="", name=None):
- text = indent + "def %s():\n" % (name or self.name)
- indent += " "
- for name in ("priority", "balance", "complete",
- "milestone", "end", "start", "effort", "load"):
- val = getattr(self, name, None)
- if val is None:
- continue
-
- if name[0] == "_":
- name = name[1:]
-
- text += "%s%s = %s\n" % (indent, name, _as_string(val))
-
- for name in self._properties:
- if name.startswith("performed"): continue
- val = getattr(self, name, None)
- try:
- if issubclass(val, resource.Resource): continue
- except TypeError:
- pass
- text += "%s%s = %s\n" % (indent, name, _as_string(val))
-
- resources = tuple(self._iter_booked_resources())
- if resources:
- text += "%sresource = \\\n" % indent
- def make_resource(res):
- return "%s %s" \
- % (indent, res.snapshot())
-
- text += "&\\\n".join(map(make_resource, resources)) + "\n"
-
- def make_resource_booking(res):
- def make_booking(booking):
- return '%s (%s, "%s", "%s", "%sM"),' \
- % (indent, res.name,
- booking.book_start.strftime("%Y%m%d %H:%M"),
- booking.book_end.strftime("%Y%m%d %H:%M"),
- booking.work_time)
-
- return "\n".join(map(make_booking, res.get_bookings(self)))
-
-
- text += "%sperformed = [\n" % indent
- text += "\n".join(map(make_resource_booking, resources)) + "]"
-
-
- child_text = map(lambda c: c.snapshot(indent), self.children)
- text += "\n\n"
- text += "".join(child_text)
-
- return text
- #@-node:snapshot
- #@+node:is_inherited
- def is_inherited(self, attrib_name):
- return not self.__dict__.has_key(attrib_name)
- #@-node:is_inherited
- #@+node:formatter
- def formatter(self, attrib_name, arg=None, format=None):
- """returns a function which is able
- to convert the value of the given attrib_name to a string"""
-
- formats = self.formats
- format = format or formats.get(attrib_name)
-
- if attrib_name in ("start", "end", "length", "effort",
- "done", "todo", "buffer", "estimated_effort",
- "performed_effort", "performed_start", "performed_end"):
- def save_strftime(v):
- try:
- return v.strftime(format)
- #except AttributeError: some bug avoid catching this exception
- except Exception:
- return str(v)
-
- return save_strftime
-
- if attrib_name == "duration":
- def save_strftime(v):
- try:
- return v.strftime(format, True)
- except AttributeError:
- return str(v)
-
- return save_strftime
-
- if attrib_name in ("booked_resource", "performed_resource"):
- def get_resource_name(v):
- title = getattr(v, "title", None)
- if title: return title
- return ", ".join([r.title for r in v])
- return get_resource_name
-
- if arg and attrib_name in ("costs", "sum", "max", "min"):
- format = formats.get("%s(%s)" % (attrib_name, arg), format)
-
- if format:
- return lambda v: locale.format(format, v, True)
-
- return str
- #@-node:formatter
- #@-node:Public methods
- #@+node:Resource allocation Methods
- #@+node:_all_resources_as_dict
- def _all_resources_as_dict(self):
- if self.children:
- result = {}
- for c in self.children:
- result.update(c._all_resources_as_dict())
-
- return result
-
- if self.resource:
- return dict(map(lambda r: (r, 1), self.resource.all_members()))
-
- return {}
- #@-node:_all_resources_as_dict
- #@+node:_test_allocation
- def _test_allocation(self, resource_state, allocator):
- resource = self.resource._get_resources(resource_state)
- if not resource:
- return False
-
- return allocator.test_allocation(self, resource)
- #@-node:_test_allocation
- #@+node:_allocate
- def _allocate(self, state, allocator):
- allocator.allocate(self, state)
- #activate cache for done and todo
-
- if self.start.to_datetime() > self.end.to_datetime():
- #this can happen when performed effort are
- #during non working time
- tmp = self.start
- self.start = self.end
- self.end = tmp
-
- for r in self.performed_resource:
- r.correct_bookings(self)
-
- self._resource_length = map(lambda r: (weakref.proxy(r), \
- r.length_of(self)),
- self._iter_booked_resources())
- #@-node:_allocate
- #@+node:_convert_performed
- def _convert_performed(self, all_resources):
- performed = self.performed
- if not performed: return False
-
- if not isinstance(performed, (tuple, list)) \
- or not isinstance(performed[0], (tuple, list)) \
- or not len(performed[0]) >= 3:
- self._raise(TypeError("""The format of the performed attribute must be:
- [( res_name, start_literal, end_literal, working_time ), ... ].
- """), "performed")
-
- round_down_delta = self.root.calendar.minimum_time_unit / 2
- round_down_delta = datetime.timedelta(minutes=round_down_delta)
-
- def convert_item(index):
- item = performed[index]
- res, start, end = item[:3]
- if isinstance(res, str):
- found = filter(lambda r: r.name == res, all_resources)
- if found: res = found[0]
-
- try:
- if not isinstance(res, (resource.Resource,
- resource._MetaResource)):
- raise ValueError("the resource '%s' is unknown." % res)
-
- start = _to_datetime(start)
- end = _to_datetime(end)
-
- if len(item) > 3:
- working_time = self._to_delta(item[3]).round()
- else:
- working_time = self._to_delta(end - start, True)
-
- return ((res, start, end, working_time), index)
- except Exception, exc:
- self._raise(exc.__class__("Item %i: %s" \
- % (index + 1, str(exc))),
- "performed")
-
- converted = dict(map(convert_item, range(len(performed))))
- converted = converted.items()
- converted.sort()
-
- #check for overlapping items
- last_res = None
- for item, index in converted:
- res, start, end, work_time = item
- if last_res == res and start < last_end:
- self._warn("Items %i, %i: %s and %s are overlapping." \
- % (last_index + 1, index + 1,
- str(performed[last_index]),
- str(performed[index])),
- "performed")
-
- last_res = res
- last_end = end
- last_index = index
-
- self._performed = map(lambda x: x[0], converted)
- return True
- #@-node:_convert_performed
- #@+node:_allocate_performed
- def _allocate_performed(self, performed):
- if not performed: return
-
- to_delta = self._to_delta
- to_start = self._to_start
- to_end = self._to_end
-
- last = datetime.datetime.min
- first = datetime.datetime.max
- effort = 0
- work_time_sum = 0
- zero_minutes = to_delta(0)
- minimum_time_unit = to_delta(self.calendar.minimum_time_unit)
- summary = {}
-
- for item in performed:
- res, start, end, work_time = item
- effort += work_time * self.efficiency * res.efficiency
- work_time_sum += work_time
-
- res = res()
- ss, es, wts = summary.get(res, (datetime.datetime.max,
- datetime.datetime.min,
- zero_minutes))
- summary[res] = (min(ss, start), max(es, end), wts + work_time)
-
- for r, v in summary.iteritems():
- start, end, work_time = v
- assert(start.__class__ is datetime.datetime)
- assert(end.__class__ is datetime.datetime)
-
- #the booking limits should be inside the workingtime
- #to display them correct in resource charts
- cstart = to_start(start).to_datetime()
- if cstart > start: cstart = to_end(start).to_datetime()
-
- cend = to_end(end).to_datetime()
- if cend < end: cend = to_start(end).to_datetime()
-
- if self.root.is_snapshot:
- delta = to_end(cend) - to_start(cstart)
- else:
- delta = to_delta(cend - cstart).round()
-
- if not delta:
- delta = minimum_time_unit
-
- book_load = float(work_time) / delta
- r().book_task(self, cstart, cend, book_load, work_time, True)
- last = max(end, last)
- first = min(start, first)
-
- self._performed_resource_length = tuple([ (r, v[2]) for r, v in summary.iteritems() ])
- self.performed_resource = tuple(summary.keys())
- self.performed_end = last
- self.performed_start = first
- self.performed_effort = to_delta(effort)
- self.performed_work_time = to_delta(work_time_sum)
- self._check_completion()
- #@-node:_allocate_performed
- #@+node:_iter_booked_resources
- def _iter_booked_resources(self):
- result = dict(map(lambda r: (r, 1), self.performed_resource))
- result.update(dict(map(lambda r: (r, 1), self.booked_resource)))
- return result.iterkeys()
- #@-node:_iter_booked_resources
- #@-node:Resource allocation Methods
- #@+node:Compile Methods
- #@+node:_generate
- def _generate(self, deferred=None):
- do_raise = False
- deferred = deferred or [ self ]
- while deferred:
- new_deferred = []
- for task in deferred:
- task._compile(new_deferred, do_raise)
-
- do_raise = deferred == new_deferred
- deferred = new_deferred
- #@-node:_generate
- #@+node:_recalc_properties
- def _recalc_properties(self):
- if not self._properties: return
- self.__compile_function([], False, _MeProxyRecalc(self))
- self._is_compiled = True
- #@-node:_recalc_properties
- #@+node:_compile
- def _compile(self, deferred, do_raise):
- self.dont_inherit = ()
- self._constraint = None
- self._original_values.clear()
- self._properties.clear()
-
- try:
- self.__at_compile
- #@ << raise child recursion error >>
- #@+node:<< raise child recursion error >>
- self._raise(RecursionError("A child defines a "\
- "recursive definition at %s" % self.path))
- #@-node:<< raise child recursion error >>
- #@nl
- except AttributeError:
- self.__at_compile = self, ""
-
- try:
- self.__compile_function(deferred, do_raise, _MeProxy(self))
- finally:
- del self.__at_compile
-
- for c in self.children:
- if not c._is_compiled:
- c._compile(deferred, do_raise)
-
- if self._is_compiled:
- self.__check_milestone()
- self.__check_task()
- self.root.has_actual_data |= self.__dict__.has_key("performed")
-
- #@-node:_compile
- #@+node:__compile_function
- def __compile_function(self, deferred, do_raise, me_instance):
- self._is_compiled = self._is_frozen
-
- restore_globals = []
- globals_ = self._function.func_globals
-
- #@ << set function global values >>
- #@+node:<< set function global values >>
- def to_value_wrapper(a):
- if isinstance(a, _ValueWrapper):
- return a
-
- return _ValueWrapper(a, [(None, None)])
-
- def my_max(*args):
- return max(map(to_value_wrapper, args))
-
- def my_min(*args):
- return min(map(to_value_wrapper, args))
-
- globals_["me"] = me_instance
-
- if self._is_compiled:
- globals_["up"] = self.up
- globals_["root"] = self.root
- else:
- globals_["up"] = _Path(self.up, "up")
- globals_["root"] = _Path(self.root, "root")
-
- globals_["Delta"] = self._to_delta
- globals_["Date"] = self._to_start
- globals_["max"] = my_max
- globals_["min"] = my_min
- globals_["add_attrib"] = me_instance.add_attrib
- #@nonl
- #@-node:<< set function global values >>
- #@nl
- #@ << set me in global functions >>
- #@+node:<< set me in global functions >>
- #@+at
- # Is used for functions like YearlyMax, MonthlyMax, ....
- #@-at
- #@@code
- for name in self._function.global_names:
- try:
- obj = globals_[name]
- if isinstance(obj, types.FunctionType):
- fg = obj.func_globals
- if not fg.has_key("me") and "me" in obj.func_code.co_names:
- restore_globals.append(fg)
- fg["me"] = me_instance
- except KeyError: continue
- #@nonl
- #@-node:<< set me in global functions >>
- #@nl
- try:
- #@ << eval function >>
- #@+node:<< eval function >>
- if do_raise:
- try:
- self._function()
- self._is_compiled = True
- except _IncompleteError, e:
- src = e.args[1]
- if src is not self:
- self.__at_compile = e.args[1:]
- src._compile([], True)
-
- raise
- else:
- try:
- self._function()
- self._is_compiled = True
- except AttributeError, e:
- #print "AttributeError:", e, self.name, e.is_frozen, do_raise
- deferred.append(self)
- except _IncompleteError:
- #print "_IncompleteError:", id(self), self.name, do_raise
- deferred.append(self)
- except RecursionError:
- self._is_parent_referer = True
- deferred.append(self)
- #@nonl
- #@-node:<< eval function >>
- #@nl
- finally:
- for fg in restore_globals:
- del fg["me"]
-
- #@-node:__compile_function
- #@-node:Compile Methods
- #@+node:Setting methods
- #@+node:_set_attrib
- def _set_attrib(self, name, value):
- if value is _NEVER_USED_: return
-
- try:
- value = self._setting_hooks[name](self, name, value)
- except KeyError: pass
-
- if name == "__constraint__":
- self._constraint = value
- return
-
- if type(value) == types.FunctionType:
- if value.func_code.co_argcount == 0:
- #@ << add child task >>
- #@+node:<< add child task >>
- try:
- task = self.__dict__[value.func_name]
- except KeyError:
- task = Task(value, value.func_name, self, len(self.children) + 1)
- self.children.append(task)
- setattr(self, value.func_name, task)
- return
- #@nonl
- #@-node:<< add child task >>
- #@nl
-
- if name[0] == "_":
- #private vars will not be set
- return
-
- if isinstance(value, _Path):
- value = value._task
-
- set_method = getattr(self, "_set_" + name, None)
- if set_method:
- #@ << set standard attribute >>
- #@+node:<< set standard attribute >>
- if type(value) == types.DictionaryType:
- self.root.all_scenarios.update(value.keys())
- value = value.get(self.scenario, value["_default"])
-
- self.__set_sources(name, value)
- self._original_values[name] = value
- set_method(_val(value))
- #@nonl
- #@-node:<< set standard attribute >>
- #@nl
- else:
- #@ << set userdefined attribute >>
- #@+node:<< set userdefined attribute >>
- if callable( getattr(self.__class__, name, None)):
- raise NameError('You may not use "%s" as attribute' % name)
-
- setattr(self, name, value)
- self._properties[name] = True
- self.__set_sources(name, value)
- #@nonl
- #@-node:<< set userdefined attribute >>
- #@nl
- #@-node:_set_attrib
- #@+node:read only attributes
- #@+node:_set_name
- def _set_name(self, value):
- raise AttributeError("The attribute 'name' is readonly.")
- #@nonl
- #@-node:_set_name
- #@+node:_set_done
- def _set_done(self, value):
- raise AttributeError("The attribute 'done' is readonly.")
- #@nonl
- #@-node:_set_done
- #@+node:_set_performed_work_time
- def _set_performed_work_time(self, value):
- raise AttributeError("The attribute 'performed_work_time' is readonly.")
- #@nonl
- #@-node:_set_performed_work_time
- #@+node:_set_booked_resource
- def _set_booked_resource(self, value):
- raise AttributeError("The attribute 'booked_resource' is readonly.")
- #@nonl
- #@-node:_set_booked_resource
- #@+node:_set_performed_effort
- def _set_performed_effort(self, value):
- raise AttributeError("The attribute 'performed_effort' is readonly.")
- #@nonl
- #@-node:_set_performed_effort
- #@+node:_set_children
- def _set_children(self, value):
- raise AttributeError("The attribute 'children' is readonly.")
- #@nonl
- #@-node:_set_children
- #@+node:_set_depth
- def _set_depth(self, value):
- raise AttributeError("The attribute 'depth' is readonly.")
- #@nonl
- #@-node:_set_depth
- #@+node:_set_index
- def _set_index(self, value):
- raise AttributeError("The attribute 'index' is readonly.")
- #@nonl
- #@-node:_set_index
- #@+node:_set_scenario
- def _set_scenario(self, value):
- raise AttributeError("The attribute 'scenario' is readonly.")
- #@nonl
- #@-node:_set_scenario
- #@+node:_set_buffer
- def _set_buffer(self, value):
- raise AttributeError("The attribute 'buffer' is readonly.")
- #@nonl
- #@-node:_set_buffer
- #@-node:read only attributes
- #@+node:_set_start
- def _set_start(self, value):
- self.__start_class = value.__class__
- self.start = self._to_start(value).round()
- #@-node:_set_start
- #@+node:_set_end
- def _set_end(self, value):
- self.end = self._to_end(value)
- #@-node:_set_end
- #@+node:_set_max_load
- def _set_max_load(self, max_load):
- self.max_load = float(max_load)
- #@-node:_set_max_load
- #@+node:_set_load
- def _set_load(self, load):
- self.load = float(load)
- #@-node:_set_load
- #@+node:_set_length
- def _set_length(self, value):
- self.length = self._to_delta(value).round()
- #@-node:_set_length
- #@+node:_set_effort
- def _set_effort(self, value):
- self.effort = self._to_delta(value).round()
- #@-node:_set_effort
- #@+node:_set_duration
- def _set_duration(self, value):
- self.duration = self._to_delta(value, True).round()
- #@-node:_set_duration
- #@+node:_set_complete
- def _set_complete(self, value):
- self.complete = value
- #@-node:_set_complete
- #@+node:_set_done
- def _set_done(self, value):
- self.done = self._to_delta(value).round()
- #@-node:_set_done
- #@+node:_set_todo
- def _set_todo(self, value):
- self.todo = self._to_delta(value).round()
- #@-node:_set_todo
- #@+node:_set_milestone
- def _set_milestone(self, value):
- self.milestone = value
- #@-node:_set_milestone
- #@+node:_set_resource
- def _set_resource(self, value):
- if not value:
- self.resource = None
- return
-
- if isinstance(value, (tuple, list)):
- value = reduce(lambda a, b: a & b, value)
-
- self.resource = value()
-
- #@-node:_set_resource
- #@+node:_set_copy_src
- def _set_copy_src(self, value):
- if isinstance(value, _MeProxy):
- raise RuntimeError("Cannot copy me.")
-
- if not value._is_compiled:
- raise _IncompleteError(value, "copy_src")
-
- if value.resource and not self.resource:
- self.resource = value.resource
-
- if value.balance and not self.balance:
- self.balance = value.balance
-
- copy_parms = ("priority", "todo", "complete",
- "_constraint", "load", "length",
- "effort", "duration")
-
- for p in copy_parms:
- v = value.__dict__.get(p)
- if v: setattr(self, p, v)
-
- self.copy_src = value
- self._properties.update(value._properties)
- for k in value._properties.iterkeys():
- setattr(self, k, getattr(value, k))
- #@-node:_set_copy_src
- #@+node:__set_sources
- def __set_sources(self, attrib_name, value):
- #@ << find references >>
- #@+node:<< find references >>
- def make_ref(val):
- if isinstance(val, _ValueWrapper):
- return val._ref
-
- if isinstance(val, Task):
- return [(val, "")]
-
- return []
-
- if isinstance(value, (list, tuple)):
- sources = _refsum(map(make_ref, value))
- else:
- sources = make_ref(value)
- #@nonl
- #@-node:<< find references >>
- #@nl
- if not sources: return
-
- #track only dependcies within the same project
- root = self.root
- sources = [ task.path + "." + attrib
- for task, attrib in sources
- if task and task.root is root ]
- self._sources[attrib_name] = tuple(sources)
- attr_path = self.path + "." + attrib_name
-
- #set dependencies of my sources
- for d in sources:
- path, attrib = _split_path(d)
- task = self.get_task(path)
- r_d = task._dependencies
- d_l = r_d.setdefault(attrib, {})
- d_l[attr_path] = True
- #@-node:__set_sources
- #@+node:Calendar Setters
- #@+node:_set_calendar
- def _set_calendar(self, value):
- self.calendar = value
- self._to_delta = value.Minutes
- self._to_start = value.StartDate
- self._to_end = value.EndDate
- self.__renew_dates()
-
- #@-node:_set_calendar
- #@+node:__renew_dates
- def __renew_dates(self):
- for attrib in ("effort", "start", "end", "length", "todo"):
- try:
-
- self._set_attrib(attrib, self._original_values[attrib])
- except KeyError:
- pass
-
- #@-node:__renew_dates
- #@+node:__make_calendar
- def __make_calendar(self):
- if not "calendar" in self.__dict__:
- cal = self.calendar = self.calendar.clone()
- self._to_delta = cal.Minutes
- self._to_start = cal.StartDate
- self._to_end = cal.EndDate
- #@nonl
- #@-node:__make_calendar
- #@+node:_set_vacation
- def _set_vacation(self, value):
- self.__make_calendar()
- self.calendar.set_vacation(value)
- self._properties["vacation"] = True
- self.vacation = value
- self.__renew_dates()
- #@-node:_set_vacation
- #@+node:_set_extra_work
- def _set_extra_work(self, value):
- self.__make_calendar()
- self.calendar.set_extra_work(value)
- self._properties["extra_work"] = True
- self.extra_work = value
- self.__renew_dates()
- #@-node:_set_extra_work
- #@+node:_set_working_days
- def _set_working_days(self, value):
-
- if type(value[0]) is str:
- value = (value, )
-
- self.working_days = value
- self._properties["working_days"] = True
- self.__make_calendar()
-
- for v in value:
- day_range = v[0]
- tranges = tuple(v[1:])
- self.calendar.set_working_days(day_range, *tranges)
-
- self.__renew_dates()
- #@nonl
- #@-node:_set_working_days
- #@+node:_set_minimum_time_unit
- def _set_minimum_time_unit(self, value):
- self.__make_calendar()
- self.calendar.minimum_time_unit = value
- self._properties["minimum_time_unit"] = True
- #@-node:_set_minimum_time_unit
- #@+node:_get_minimum_time_unit
- def _get_minimum_time_unit(self):
- return self.calendar.minimum_time_unit
-
- minimum_time_unit = property(_get_minimum_time_unit)
- #@-node:_get_minimum_time_unit
- #@+node:_set_working_days_per_week
- def _set_working_days_per_week(self, value):
-
- self.__make_calendar()
- self.calendar.working_days_per_week = value
- self._properties["working_days_per_week"] = True
- #@-node:_set_working_days_per_week
- #@+node:_get_working_days_per_week
- def _get_working_days_per_week(self):
- return self.calendar.working_days_per_week
-
- working_days_per_week = property(_get_working_days_per_week)
- #@-node:_get_working_days_per_week
- #@+node:_set_working_days_per_month
- def _set_working_days_per_month(self, value):
- self.__make_calendar()
- self.calendar.working_days_per_month = value
- self._properties["working_days_per_month"] = True
- #@-node:_set_working_days_per_month
- #@+node:_get_working_days_per_month
- def _get_working_days_per_month(self):
- return self.calendar.working_days_per_month
-
- working_days_per_month = property(_get_working_days_per_month)
- #@-node:_get_working_days_per_month
- #@+node:_set_working_days_per_year
- def _set_working_days_per_year(self, value):
- self.__make_calendar()
- self.calendar.working_days_per_year = value
- self._properties["working_days_per_year"] = True
- #@-node:_set_working_days_per_year
- #@+node:_get_working_days_per_year
- def _get_working_days_per_year(self):
- return self.calendar.working_days_per_year
-
- working_days_per_year = property(_get_working_days_per_year)
- #@-node:_get_working_days_per_year
- #@+node:_set_working_hours_per_day
- def _set_working_hours_per_day(self, value):
- self.__make_calendar()
- self.calendar.working_hours_per_day = value
- self._properties["set_working_hours_per_day"] = True
- #@-node:_set_working_hours_per_day
- #@+node:_get_working_hours_per_day
- def _get_working_hours_per_day(self):
- return self.calendar.working_hours_per_day
-
- working_hours_per_day = property(_get_working_hours_per_day)
- #@-node:_get_working_hours_per_day
- #@+node:_set_now
- def _set_now(self, value):
- proxy = weakref.proxy
- self.calendar.now = _to_datetime(value)
- #@-node:_set_now
- #@-node:Calendar Setters
- #@-node:Setting methods
- #@+node:Freezer Methods
- #@+node:_unfreeze
- def _unfreeze(self, attrib_name):
- if self.__dict__.has_key(attrib_name):
- del self.__dict__[attrib_name]
- #@-node:_unfreeze
- #@+node:_wrap_attrib
- def _wrap_attrib(self, method):
- attrib_name = method.__name__[7:]
- recursion_attrib = "_rec" + attrib_name
-
- try:
- dest, dattr = self.__at_compile
- raise RecursionError("Recursive definition of %s(%s) and %s(%s)." \
- % (self.path, attrib_name, dest.path, dattr))
- except AttributeError: pass
-
- if not self._is_compiled:
- raise _IncompleteError(self, attrib_name)
-
- try:
- getattr(self, recursion_attrib)
- raise RecursionError(self, attrib_name)
- except AttributeError: pass
-
- setattr(self, recursion_attrib, True)
-
- try:
- result = method(self)
-
- if self._is_frozen:
- setattr(self, attrib_name, result)
-
- return result
- finally:
- delattr(self, recursion_attrib)
- #@-node:_wrap_attrib
- #@+node:_find_frozen
- def _find_frozen(self, attrib_name, default=None):
- value = self.__dict__.get(attrib_name)
- if value is not None:
- return value
-
- up = self.up
- return up and up._find_frozen(attrib_name) or default
- #@-node:_find_frozen
- #@-node:Freezer Methods
- #@+node:Calculation Methods
- #@+node:__calc_performed_effort
- def __calc_performed_effort(self):
- if self.children:
- return self._to_delta(sum([ t.performed_effort for t in self.children ]))
-
- return pcalendar.Minutes(0)
-
- performed_effort = _TaskProperty(__calc_performed_effort)
- #@-node:__calc_performed_effort
- #@+node:__calc_estimated_effort
- def __calc_estimated_effort(self):
- if self.children:
- return self._to_delta(sum([ t.estimated_effort for t in self.children ]))
-
- return self.effort
-
- estimated_effort = _TaskProperty(__calc_estimated_effort)
- #@-node:__calc_estimated_effort
- #@+node:__calc_start
- def __calc_start(self):
- to_start = self._to_start
-
- if self.children:
- try:
- return min([ to_start(t.start) for t in self.children
- if not t._is_parent_referer ])
- except ValueError:
- #@ << raise child recursion error >>
- #@+node:<< raise child recursion error >>
- self._raise(RecursionError("A child defines a "\
- "recursive definition at %s" % self.path))
- #@-node:<< raise child recursion error >>
- #@nl
-
- try:
- end = self.end
- duration = self.__dict__.get("duration")
- if duration is not None:
- start = end.to_datetime() - datetime.timedelta(minutes=duration)
- else:
- start = end - self.length
-
- return to_start(start)
-
- except RecursionError:
- start = self._find_frozen("start")
- if start: return to_start(start)
- #@ << raise recursion error >>
- #@+node:<< raise recursion error >>
- raise RecursionError("you have to specify a "\
- "start or an end at %s" % self.path)
- #@nonl
- #@-node:<< raise recursion error >>
- #@nl
-
- start = _TaskProperty(__calc_start)
-
- #@-node:__calc_start
- #@+node:__calc_end
- def __calc_end(self):
- to_end = self._to_end
-
- if self.children:
- try:
- return max([ to_end(t.end) for t in self.children
- if not t._is_parent_referer ])
- except ValueError:
- #@ << raise child recursion error >>
- #@+node:<< raise child recursion error >>
- self._raise(RecursionError("A child defines a "\
- "recursive definition at %s" % self.path))
- #@-node:<< raise child recursion error >>
- #@nl
-
- try:
- start = self.start
- duration = self.__dict__.get("duration")
- if duration is not None:
- end = start.to_datetime() + datetime.timedelta(minutes=duration)
- else:
- end = start + self.length
-
- return to_end(end)
-
- except RecursionError:
- end = self._find_frozen("end")
- if end: return to_end(end)
- #@ << raise recursion error >>
- #@+node:<< raise recursion error >>
- raise RecursionError("you have to specify a "\
- "start or an end at %s" % self.path)
- #@nonl
- #@-node:<< raise recursion error >>
- #@nl
-
-
- end = _TaskProperty(__calc_end)
- #@-node:__calc_end
- #@+node:__calc_load
- def __calc_load(self):
- length = self.__dict__.get("length")
- effort = self.__dict__.get("effort")
-
- if length is not None and effort is not None:
- return float(effort) / (float(length) or 1.0)
-
- load = self._find_frozen("load")
- if load is not None: return load
- return 1.0
-
- load = _TaskProperty(__calc_load)
- #@-node:__calc_load
- #@+node:__calc_length
- def __calc_length(self):
- effort = self.__dict__.get("effort")
- if effort is None:
- return self.end - self.start
-
- return self._to_delta(effort / self.load)
-
- length = _RoundingTaskProperty(__calc_length, "length")
- #@-node:__calc_length
- #@+node:__calc_duration
- def __calc_duration(self):
- return self._to_delta(self.end.to_datetime()\
- - self.start.to_datetime(), True)
-
- duration = _TaskProperty(__calc_duration)
- #@-node:__calc_duration
- #@+node:__calc_effort
- def __calc_effort(self):
- if self.children:
- return self._to_delta(sum([ t.effort for t in self.children ]))
-
- return self._to_delta(self.length * self.load)
-
- effort = _RoundingTaskProperty(__calc_effort, "effort")
- #@-node:__calc_effort
- #@+node:__calc_done
- def __calc_done(self):
- if self.children:
- dones = map(lambda t: t.done, self.children)
- return self._to_delta(sum(dones))
-
- res = self._iter_booked_resources()
- done = sum(map(lambda r: r.done_of(self), res))
-
- complete = self.__dict__.get("complete")
- todo = self.__dict__.get("todo")
-
- if not done and complete == 100 or todo == 0:
- #if now is not set
- done = self.effort
-
- return self._to_delta(done)
-
- done = _TaskProperty(__calc_done)
- #@-node:__calc_done
- #@+node:__calc_buffer
- def __calc_buffer(self):
- if self.children:
- return self._to_delta(min(map(lambda t: t.buffer, self.children)))
-
- scenario = self.scenario
- end = self.end
- old_end = self.__dict__.get("end")
-
- #@ << find all tasks, that depend on my end >>
- #@+node:<< find all tasks, that depend on my end >>
- deps = { }
- task = self
- while task:
- deps.update(task._dependencies.get("end", {}))
- task = task.up
- #@nonl
- #@-node:<< find all tasks, that depend on my end >>
- #@nl
-
- #@ << define unfreeze_parents >>
- #@+node:<< define unfreeze_parents >>
- def unfreeze_parents():
- task = self.up
- while task:
- task._unfreeze("end")
- task = task.up
- #@nonl
- #@-node:<< define unfreeze_parents >>
- #@nl
-
- buffers = [ ]
- for d in deps.keys():
- path, attrib = _split_path(d)
- if attrib != "start":
- continue
-
- #@ << calculate buffer to descendant 'd' >>
- #@+node:<< calculate buffer to descendant 'd' >>
- unfreeze_parents()
-
- # the following code considers a expressione like
- # start = predecessor.end + Delta("1d") the buffer
- # calculation must be aware of the 1d delay.
- # (therefore a simple succ_start - end would be
- # incorrect)
- # Solution: Simluate a later end and calculate the
- # real delay
-
- succ_task = self.get_task(path)
- simulated_task = Task(succ_task._function,
- succ_task.name,
- succ_task.up, 1)
-
- current_start = succ_task.start
- simulated_end = current_start
- self.end = current_start
-
- simulated_task._generate()
- simulated_start = simulated_task.start
-
- unfreeze_parents()
- if old_end: self.end = old_end
- else: self._unfreeze("end")
- del simulated_task
-
- current_delay = current_start - end
- simulated_delay = simulated_start - simulated_end
- real_delay = current_delay - simulated_delay
- try:
- buffer_ = real_delay + succ_task.buffer
- except RecursionError, err:
- self._raise(err)
- #@nonl
- #@-node:<< calculate buffer to descendant 'd' >>
- #@nl
-
- buffers.append(buffer_)
- if not buffer_:
- break
-
- if buffers:
- return self._to_delta(min(buffers))
-
- return not self.milestone \
- and self.root.end - end \
- or self._to_delta(0)
-
- buffer = _TaskProperty(__calc_buffer)
- #@-node:__calc_buffer
- #@+node:__calc_complete
- def __calc_complete(self):
- done = self.done
- todo = self.todo
- return int(100.0 * done / ((done + todo) or 1))
-
- complete = _TaskProperty(__calc_complete)
- #@-node:__calc_complete
- #@+node:__calc_todo
- def __calc_todo(self):
- complete = self.__dict__.get("complete")
- if complete:
- # effort = done + todo
- # done done
- # complete = ------ ==> todo = -------- - done
- # effort complete
- complete = float(complete)
- done = self.done
- if done:
- done = float(done)
- return self._to_delta(done * 100.0 / complete - done)
- return self._to_delta(self.effort * complete / 100.0)
-
- if self.children:
- todos = map(lambda t: t.todo, self.children)
- return self._to_delta(sum(todos))
-
- todo = sum(map(lambda r: r.todo_of(self), self.booked_resource))
- return self._to_delta(max(todo, self.effort - self.done))
-
- todo = _TaskProperty(__calc_todo)
- #@-node:__calc_todo
- #@-node:Calculation Methods
- #@+node:Check Methods
- #@+node:__check_task
- def __check_task(self):
- if self.children: return
-
- start = self._find_frozen("start")
- end = self._find_frozen("end")
-
- if not (start or end):
- self._raise(ValueError("You must specify either a"\
- " start or an end attribute"))
-
- if start and end: return
-
- length = self.__dict__.get("length")
- duration = self.__dict__.get("duration")
- effort = self.__dict__.get("effort")
- if not (effort or length or duration):
- #set a default value
- self._set_effort("1d")
- #self._raise(ValueError("You must specify either a"\
- # " length or a duration or "\
- # "an effort attribute"))
- #@-node:__check_task
- #@+node:__check_milestone
- def __check_milestone(self):
- if not self.milestone: return
-
- self.length = self._to_delta(0)
- start = self.__dict__.get("start")
- if not start:
- self._raise(ValueError("Milestone must have start attribute"),
- "milstone")
-
- if self.__start_class.__name__ == "edt":
- #the milestone is probably dependent on the end date of
- #an other task (see edt in pcalendar) ==> start at the end date
- self.start = self.end = self._to_end(self.start)
- else:
- self.start = self.end = self._to_start(self.start)
-
- #@-node:__check_milestone
- #@+node:_check_completion
- def _check_completion(self):
- if not self.performed_effort: return
- if self.root.is_snapshot: return
-
- # allocation is not done yet ==> self.todo, self.done,
- # self.complete cannot be calculated
- if self._find_frozen("complete", 0) < 100 \
- and self.__dict__.get("todo", 1) > 0:
- return
-
- start = self.performed_start
- end = self.performed_end
- #ensure that self.start.to_datetime() < self.end.to_datetime()
- cstart = self._to_start(start)
- if cstart.to_datetime() > start: cstart = self._to_end(start)
-
- cend = self._to_end(end)
- if cend.to_datetime() < end: cend = self._to_start(end)
-
- self.start = cstart
- self.end = cend
-
- if self.performed_effort != self.effort:
- self.estimated_effort = self.effort
- self.effort = self.performed_effort
- #@-node:_check_completion
- #@+node:check
- def check(self):
- if self._constraint and self._is_compiled:
- globals_ = self._function.func_globals
- globals_["me"] = self
- globals_["up"] = self.up
- globals_["root"] = self.root
- globals_["assert_"] = self.__assert
- self._constraint()
- #@-node:check
- #@-node:Check Methods
- #@+node:Error Methods
- #@+node:__assert
- def __assert(self, value):
- if not value:
- warnings.warn('Assertion in scenario: "%s".' % self.scenario,
- RuntimeWarning, 2)
- #@-node:__assert
- #@+node:_warn
- def _warn(self, message, attrib=None, level=2):
- self.__compile_function([], True, _MeProxyWarn(self, attrib, message))
- #@-node:_warn
- #@+node:_raise
- def _raise(self, exc, attrib=None):
- self.__compile_function([], True, _MeProxyError(self, attrib, exc))
- raise exc
- #@-node:_raise
- #@-node:Error Methods
- #@-others
-#@nonl
-#@-node:class Task
-#@-node:Task
-#@+node:Projects
-#@+node:class _ProjectBase
-class _ProjectBase(Task):
- """
- Base class for all projects.
- """
- #@ << class _ProjectBase declarations >>
- #@+node:<< class _ProjectBase declarations >>
- __attrib_completions__ = { }
- __attrib_completions__.update(Task.__attrib_completions__)
- del __attrib_completions__["milestone"] #project cannot be milestones
-
- priority = 500
- efficiency = 1.0
- max_load = 1.0
- balance = 0
- resource = None
- copy_src = None
- has_actual_data = False
- is_snapshot = False
-
- #@-node:<< class _ProjectBase declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, top_task, scenario="_default", id=""):
- self.calendar = pcalendar.Calendar()
- Task.__init__(self, top_task, top_task.func_name)
- self.id = id or self.name
- self.scenario = scenario
- self.all_scenarios = set(("_default",))
- self.path = "root"
- self._globals = top_task.func_globals.copy()
- self._generate()
-
- #@-node:__init__
- #@+node:_idendity_
- def _idendity_(self): return self.id
- #@-node:_idendity_
- #@+node:_restore_globals
- def _restore_globals(self):
- self._function.func_globals.clear()
- self._function.func_globals.update(self._globals)
- del self._globals
- #@-node:_restore_globals
- #@+node:free
- def free(self):
- all_resources = self.all_resources()
- for r in all_resources:
- r().unbook_tasks_of_project(self.id, self.scenario)
-
- for t in self:
- t.booked_resource = ()
-
- return all_resources
- #@-node:free
- #@+node:_get_balancing_list
- def _get_balancing_list(self):
-
- try:
- cached_list = balancing_cache[self._function.org_code]
- if len(cached_list) != len(tuple(self)):
- # different scenarios can have different tasks
- raise KeyError()
-
- except KeyError:
- cached_list = _build_balancing_list(self)
- balancing_cache[self._function.org_code] = cached_list
- else:
- cached_list = [ self.get_task(t.path) for t in cached_list ]
-
- return cached_list
- #@-node:_get_balancing_list
- #@+node:snapshot
- def snapshot(self, indent="", name=None):
- text = Task.snapshot(self, indent, name)
-
- lines = text.splitlines(True)
- indent += " "
-
- def make_resource(r):
- return '%sclass %s(Resource): title = "%s"\n' \
- % (indent, r.name, r.title)
-
- now = datetime.datetime.now().strftime("%x %H:%M")
- resource_text = map(lambda r: make_resource(r), self.all_resources())
- lines.insert(1, "%sfrom faces import Resource\n" % indent)
- lines.insert(2, "".join(resource_text) + "\n")
- lines.insert(3, '%snow = "%s"\n' % (indent, now))
- lines.insert(4, '%sis_snapshot = True\n' % indent)
- return "".join(lines)
- #@-node:snapshot
- #@-others
-#@-node:class _ProjectBase
-#@+node:class Project
-class Project(_ProjectBase):
- """
- Generates a Project without allocating resources.
-
- @param top_task: Specifies the highest function of a project definiton.
-
- @param scenario: Specifies the name of the scenario which should be scheduled.
-
- @param id: Specifiess a unique idenfication name to distinguish the project from
- other projects in the resource database. The default value for id
- is the name of top_task.
- """
- #@ << class Project declarations >>
- #@+node:<< class Project declarations >>
- __call_completion__ = 'Project(|top_task, scenario="_default", id=None)'
-
- #@-node:<< class Project declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, top_task, scenario="_default", id=None):
- _ProjectBase.__init__(self, top_task, scenario, id)
- no_snapshot = not self.is_snapshot
- for t in self:
- t._is_frozen = True
- t._recalc_properties()
- no_snapshot and t.check()
-
- self._restore_globals()
-
- #@-node:__init__
- #@-others
-
-#@-node:class Project
-#@+node:class _AllocationPoject
-class _AllocationPoject(_ProjectBase):
- #@ @+others
- #@+node:unfreeze_parents
- def unfreeze_parents(self):
- if self.has_actual_data:
- for t in filter(lambda t: t.children, self):
- if not t._original_values.has_key("start"): t._unfreeze("start")
- if not t._original_values.has_key("end"): t._unfreeze("end")
- #@-node:unfreeze_parents
- #@-others
-#@-node:class _AllocationPoject
-#@+node:class BalancedProject
-class BalancedProject(_AllocationPoject):
- """
- Generates a project with allocated resources. The tasks are balanced
- to fit the resources load conditions.
- """
- #@ << class BalancedProject declarations >>
- #@+node:<< class BalancedProject declarations >>
- __call_completion__ = """BalancedProject(|top_task, scenario="_default",
- id=None, balance=SMART, performed=None)"""
-
- #@-node:<< class BalancedProject declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, top_task, scenario="_default",
- id=None, balance=SMART, performed=None):
- _AllocationPoject.__init__(self, top_task, scenario, id)
- self.balance = balance
- if performed:
- self._distribute_performed(performed)
- self.has_actual_data = True
-
- no_snapshot = not self.is_snapshot
- if no_snapshot:
- self.allocate()
- else:
- self.allocate_snapshot()
-
- for t in self:
- t._is_frozen = True
- t._recalc_properties()
- no_snapshot and t.check()
-
- self._restore_globals()
- #@nonl
- #@-node:__init__
- #@+node:allocate_snapshot
- def allocate_snapshot(self):
- all_resources = self.free()
- scenario = self.scenario
- has_actual_data = True
- for t in self:
- if not t.resource or t.milestone or t.children:
- continue
-
- t._convert_performed(all_resources)
- t._allocate_performed(t._performed)
- #@-node:allocate_snapshot
- #@+node:allocate
- def allocate(self):
- all_resources = self.free()
- balancing_list = self._get_balancing_list()
- scenario = self.scenario
-
- #for t in balancing_list:
- # print t.path
-
- for t in balancing_list:
- t._compile([], True)
-
- if not t.resource or t.milestone or t.children:
- continue
-
- if t._convert_performed(all_resources):
- has_actual_data = True
-
- try:
- t._allocate_performed(t._performed)
- except AttributeError:
- pass
-
- allocator = _allocators[t.balance]
- min_val = None
- min_state = None
- for p in range(t.resource._permutation_count()):
- state = t._test_allocation(p, allocator)
-
- if not state: continue
-
- to_minimize = state[0]
- if not min_val or min_val > to_minimize:
- min_val = to_minimize
- min_state = state
-
- if min_state:
- t._allocate(min_state, allocator)
- elif t.performed_start:
- # t could not be allocated ==>
- # performance data holds all information
- t.start = t._to_start(t.performed_start)
- t.end = t._to_end(t.performed_end)
-
- self.unfreeze_parents()
- #@-node:allocate
- #@+node:_distribute_performed
- def _distribute_performed(self, performed):
- project_id = self._idendity_()
- plen = len(project_id)
-
- performed = filter(lambda item: item[0].startswith(project_id),
- performed)
- performed.sort()
-
- task = None
- for item in performed:
- path = item[0]
- rpath = "root" + path[plen:]
- task = self.get_task(rpath)
-
- if not task:
- #@ << extract task in activity path >>
- #@+node:<< extract task in activity path >>
- #@+at
- # A performed path can have sub activities appended to the
- # task path.
- # like:
- #
- # root.parent1.parent2.task.subactivity
- #
- # here rhe correct task path is:
- #
- # root.parent1.parent2.task
- #
- #@-at
- #@@code
- orpath = rpath
- while not task:
- #path can specify a sub module
- #find the correct path to the module
- try:
- last_dot = rpath.rindex(".", 0, len(rpath))
- except ValueError:
- break
-
- rpath = rpath[:last_dot]
- task = self.get_task(rpath)
-
- item = list(item)
- item.append(orpath[len(rpath):])
- #@nonl
- #@-node:<< extract task in activity path >>
- #@nl
-
- if not task or task.children:
- self._warn("The performance data contain "
- "a task with id '%s'. But such "
- "a task does not exist in your "
- "project." % path)
- continue
-
- if not isinstance(task.performed, list):
- task.performed = list(task.performed)
-
- task.performed.append(item[1:])
- #@nonl
- #@-node:_distribute_performed
- #@-others
-#@-node:class BalancedProject
-#@+node:class AdjustedProject
-class AdjustedProject(_AllocationPoject):
- """
- Generates a project with allocated resources. The tasks are
- adjusted to the actual tracking data and balanced to fit the
- resources load conditions.
- """
- #@ << class AdjustedProject declarations >>
- #@+node:<< class AdjustedProject declarations >>
- __call_completion__ = 'AdjustedProject(|base_project)'
-
- #@-node:<< class AdjustedProject declarations >>
- #@nl
- #@ @+others
- #@+node:__init__
- def __init__(self, base_project):
- _AllocationPoject.__init__(self, base_project._function,
- base_project.scenario,
- base_project.id)
-
- self.balance = base_project.balance
- self.has_actual_data = base_project.has_actual_data
- self.allocate(base_project)
- for t in self:
- t._is_frozen = True
- t._recalc_properties()
- t.check()
-
- self._restore_globals()
-
-
- #@-node:__init__
- #@+node:allocate
- def allocate(self, base):
- balancing_list = self._get_balancing_list()
- scenario = self.scenario
- cal = self.calendar
- now = cal.now
-
- #for t in balancing_list:
- # print t.path
-
- #@ << free the resources, we have to rebook >>
- #@+node:<< free the resources, we have to rebook >>
- for t in balancing_list:
- src = base.get_task(t.path)
- if src.end > now or src.complete < 100:
- for r in src._iter_booked_resources():
- r.unbook_task(src)
- #@nonl
- #@-node:<< free the resources, we have to rebook >>
- #@nl
-
- for t in balancing_list:
- src = base.get_task(t.path)
- if src.end <= now and src.complete == 100:
- #@ << copy the attribs of complete tasks >>
- #@+node:<< copy the attribs of complete tasks >>
- t.effort = src.effort
- t.load = src.load
- t.start = src.start
- t.end = src.end
- t.done = src.done
- t.todo = src.todo
- t.booked_resource = src.booked_resource
- t.performed_resource = src.performed_resource
- t._unfreeze("length")
- t._unfreeze("duration")
- #@nonl
- #@-node:<< copy the attribs of complete tasks >>
- #@nl
- continue
-
- t._compile([], True)
- if not t.resource or t.milestone or t.children:
- continue
-
- # now allocate the uncomplete tasks
- #@ << allocate performed data >>
- #@+node:<< allocate performed data >>
- try:
- t._performed = src._performed
- t._allocate_performed(t._performed)
- except AttributeError:
- pass
- #@nonl
- #@-node:<< allocate performed data >>
- #@nl
- allocator = _allocators[t.balance]
-
- if src.start >= now:
- #@ << allocate tasks, that have not begun yet >>
- #@+node:<< allocate tasks, that have not begun yet >>
- min_val = None
- min_state = None
- for p in range(t.resource._permutation_count()):
- state = t._test_allocation(p, allocator)
- if not state: continue
-
- to_minimize = state[0]
- if not min_val or min_val > to_minimize:
- min_val = to_minimize
- min_state = state
-
- if min_state:
- t._allocate(min_state, allocator)
- elif t.performed_start:
- t.start = t._to_start(t.performed_start)
- t.end = t._to_end(t.performed_end)
- #@-node:<< allocate tasks, that have not begun yet >>
- #@nl
- else:
- #@ << allocate tasks, that are allready at work >>
- #@+node:<< allocate tasks, that are allready at work >>
- if t.__dict__.has_key("effort"):
- t.effort = t._to_delta(src.done + src.todo).round()
-
- resource = src.booked_resource or src.performed_resource
- state = allocator.test_allocation(t, resource)
- if state:
- t._allocate(state, allocator)
- #@nonl
- #@-node:<< allocate tasks, that are allready at work >>
- #@nl
-
- self.unfreeze_parents()
- #@nonl
- #@-node:allocate
- #@-others
-#@-node:class AdjustedProject
-#@-node:Projects
-#@-others
-
-
-"""
- Atttribute mit Bedeutung:
-
- calendar
- --------
- minimum_time_unit |int in minutes|
- working_days_per_week |int in days |
- working_days_per_month|int in days |
- working_days_per_year |int in days |
- working_hours_per_day |int in hours |
- vacation | [ one_day, (from, to), .. ] |
- working_days
- now
-
-
-
- Task
- -----
- load
- start
- end
- length
- effort
- duration
- resource
- booked_resource
-
- milestone
- complete
- done
- todo
- priority
- efficiency
- buffer
-
- children
- depth
- index
- path
- dont_inherit
-
- performed_effort
- performed_end
- performed_start
-
- sum()
- min()
- max()
- costs()
- indent_name()
- max_load
-
- copy_src (set: copy all attributes of another task
- get: reference of copy)
-
- balance
-
- for gantt
- -----
- line
- accumulate
-
-
-
-
- Resource
- ----------
- efficiency
- load
- vacation
- max_load
-
-"""
-#@-node:@file task.py
-#@-leo
diff --git a/addons/resource/faces/timescale.py b/addons/resource/faces/timescale.py
deleted file mode 100644
index e3ce2bdf74a..00000000000
--- a/addons/resource/faces/timescale.py
+++ /dev/null
@@ -1,113 +0,0 @@
-############################################################################
-# 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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-############################################################################
-
-import faces.pcalendar as pcal
-import openerp.tools as tools
-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(tools.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)
diff --git a/addons/resource/faces/utils.py b/addons/resource/faces/utils.py
deleted file mode 100644
index 5b50b07b069..00000000000
--- a/addons/resource/faces/utils.py
+++ /dev/null
@@ -1,121 +0,0 @@
-############################################################################
-# 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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
diff --git a/addons/resource/resource.py b/addons/resource/resource.py
index bc4ffa9548d..2c62cfe5fd3 100644
--- a/addons/resource/resource.py
+++ b/addons/resource/resource.py
@@ -688,115 +688,6 @@ class resource_resource(osv.osv):
default.update(name=_('%s (copy)') % (self.browse(cr, uid, id, context=context).name))
return super(resource_resource, self).copy(cr, uid, id, default, context)
- def generate_resources(self, cr, uid, user_ids, calendar_id, context=None):
- """
- Return a list of Resource Class objects for the resources allocated to the phase.
-
- NOTE: Used in project/project.py
- """
- resource_objs = {}
- user_pool = self.pool.get('res.users')
- for user in user_pool.browse(cr, uid, user_ids, context=context):
- resource_objs[user.id] = {
- 'name' : user.name,
- 'vacation': [],
- 'efficiency': 1.0,
- }
-
- resource_ids = self.search(cr, uid, [('user_id', '=', user.id)], context=context)
- if resource_ids:
- for resource in self.browse(cr, uid, resource_ids, context=context):
- resource_objs[user.id]['efficiency'] = resource.time_efficiency
- resource_cal = resource.calendar_id.id
- if resource_cal:
- leaves = self.compute_vacation(cr, uid, calendar_id, resource.id, resource_cal, context=context)
- resource_objs[user.id]['vacation'] += list(leaves)
- return resource_objs
-
- def compute_vacation(self, cr, uid, calendar_id, resource_id=False, resource_calendar=False, context=None):
- """
- Compute the vacation from the working calendar of the resource.
-
- @param calendar_id : working calendar of the project
- @param resource_id : resource working on phase/task
- @param resource_calendar : working calendar of the resource
-
- NOTE: used in project/project.py, and in generate_resources
- """
- resource_calendar_leaves_pool = self.pool.get('resource.calendar.leaves')
- leave_list = []
- if resource_id:
- leave_ids = resource_calendar_leaves_pool.search(cr, uid, ['|', ('calendar_id', '=', calendar_id),
- ('calendar_id', '=', resource_calendar),
- ('resource_id', '=', resource_id)
- ], context=context)
- else:
- leave_ids = resource_calendar_leaves_pool.search(cr, uid, [('calendar_id', '=', calendar_id),
- ('resource_id', '=', False)
- ], context=context)
- leaves = resource_calendar_leaves_pool.read(cr, uid, leave_ids, ['date_from', 'date_to'], context=context)
- for i in range(len(leaves)):
- dt_start = datetime.datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S')
- dt_end = datetime.datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S')
- no = dt_end - dt_start
- [leave_list.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
- leave_list.sort()
- return leave_list
-
- def compute_working_calendar(self, cr, uid, calendar_id=False, context=None):
- """
- Change the format of working calendar from 'Openerp' format to bring it into 'Faces' format.
- @param calendar_id : working calendar of the project
-
- NOTE: used in project/project.py
- """
- if not calendar_id:
- # Calendar is not specified: working days: 24/7
- return [('fri', '8:0-12:0','13:0-17:0'), ('thu', '8:0-12:0','13:0-17:0'), ('wed', '8:0-12:0','13:0-17:0'),
- ('mon', '8:0-12:0','13:0-17:0'), ('tue', '8:0-12:0','13:0-17:0')]
- resource_attendance_pool = self.pool.get('resource.calendar.attendance')
- time_range = "8:00-8:00"
- non_working = ""
- week_days = {"0": "mon", "1": "tue", "2": "wed","3": "thu", "4": "fri", "5": "sat", "6": "sun"}
- wk_days = {}
- wk_time = {}
- wktime_list = []
- wktime_cal = []
- week_ids = resource_attendance_pool.search(cr, uid, [('calendar_id', '=', calendar_id)], context=context)
- weeks = resource_attendance_pool.read(cr, uid, week_ids, ['dayofweek', 'hour_from', 'hour_to'], context=context)
- # Convert time formats into appropriate format required
- # and create a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
- for week in weeks:
- res_str = ""
- day = None
- if week_days.get(week['dayofweek'],False):
- day = week_days[week['dayofweek']]
- wk_days[week['dayofweek']] = week_days[week['dayofweek']]
- else:
- raise UserError(_('Make sure the Working time has been configured with proper week days!'))
- hour_from_str = hours_time_string(week['hour_from'])
- hour_to_str = hours_time_string(week['hour_to'])
- res_str = hour_from_str + '-' + hour_to_str
- wktime_list.append((day, res_str))
- # Convert into format like [('mon', '8:00-12:00', '13:00-18:00')]
- for item in wktime_list:
- if wk_time.has_key(item[0]):
- wk_time[item[0]].append(item[1])
- else:
- wk_time[item[0]] = [item[0]]
- wk_time[item[0]].append(item[1])
- for k,v in wk_time.items():
- wktime_cal.append(tuple(v))
- # Add for the non-working days like: [('sat, sun', '8:00-8:00')]
- for k, v in wk_days.items():
- if week_days.has_key(k):
- week_days.pop(k)
- for v in week_days.itervalues():
- non_working += v + ','
- if non_working:
- wktime_cal.append((non_working[:-1], time_range))
- return wktime_cal
-
class resource_calendar_leaves(osv.osv):
_name = "resource.calendar.leaves"
diff --git a/addons/resource/test/resource.yml b/addons/resource/test/resource.yml
index 618f3227d0c..9dc703f72d3 100644
--- a/addons/resource/test/resource.yml
+++ b/addons/resource/test/resource.yml
@@ -8,14 +8,6 @@
-
I had Project of OpenERP Integration of 50 Hours with three human resource assigned on it. I have started project from this week start.
--
- First, I generate the resource detail and check the resource Efficiency assigned.
--
- !python {model: resource.resource}: |
- from openerp.tools import float_compare
- resources = self.generate_resources(cr, uid, [ref('base.user_root'),ref('base.user_demo')], ref('timesheet_group1'), context)
- for resource in resources.values():
- assert float_compare(resource.get('efficiency', 0.0), 1.0, precision_digits=2) == 0, 'Invalid resource generated.'
-
I check per day work hour availability of the Resource based on Working Calendar Assigned to each resource, for first day of the week.
-