From f1099a6598fc7381ac43af41a13f44b3e7a62874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 27 Jul 2015 14:33:39 +0200 Subject: [PATCH] [REM] resource, project: removed faces library and its related code in resource and project. This library was once used to perfom resource allocation: scheduling tasks and things like that. However this feature cannot be accessed through the interface since a long time. The feature is now removed from the project code. The library has been removed. Some code in resource used only in the project code has also been removed, as well as a test linked to this code. --- addons/project/project.py | 100 - addons/project/project_demo.xml | 4 - addons/project/tests/test_project_flow.py | 3 +- addons/resource/__init__.py | 1 - addons/resource/faces/__init__.py | 10 - addons/resource/faces/observer.py | 79 - addons/resource/faces/pcalendar.py | 951 ----- addons/resource/faces/plocale.py | 54 - addons/resource/faces/resource.py | 866 ----- addons/resource/faces/task.py | 3854 --------------------- addons/resource/faces/timescale.py | 113 - addons/resource/faces/utils.py | 121 - addons/resource/resource.py | 109 - addons/resource/test/resource.yml | 8 - 14 files changed, 1 insertion(+), 6272 deletions(-) delete mode 100644 addons/resource/faces/__init__.py delete mode 100644 addons/resource/faces/observer.py delete mode 100644 addons/resource/faces/pcalendar.py delete mode 100644 addons/resource/faces/plocale.py delete mode 100644 addons/resource/faces/resource.py delete mode 100644 addons/resource/faces/task.py delete mode 100644 addons/resource/faces/timescale.py delete mode 100644 addons/resource/faces/utils.py 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. -