work in progress: work item (task) management

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3080 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2008-12-22 21:49:43 +00:00
parent ee49d66e0d
commit 8572156c11
4 changed files with 143 additions and 13 deletions

View file

@ -91,5 +91,39 @@ We are now ready to set up the tracking storage.
The work management only deals with the IDs or names of tasks and persons, The work management only deals with the IDs or names of tasks and persons,
so we do not have to set up real objects. so we do not have to set up real objects.
>>> workItems.add('001', 'john') >>> wi01 = workItems.add('001', 'john')
<WorkItem ['001', 1, 'john', '2008-12-22 12:07', 'created']: {}> >>> wi01
<WorkItem ['001', 1, 'john', '...', 'created']:
{'created': ..., 'creator': 'john'}>
Properties that have not been set explicitly default to None; properties
not specified in the IWorkItem interface will lead to an AttributeError.
>>> wi01.description is None
True
>>> wi01.something
Traceback (most recent call last):
...
AttributeError: something
Certain (not all) properties may be set after creation.
>>> wi01.setInitData(planStart=1229955772, planDuration=600, party='jim')
>>> wi01
<WorkItem ['001', 1, 'jim', '2008-12-22 14:22', 'created']:
{'created': ..., 'planEnd': 1229956372, 'planDuration': 600,
'planStart': 1229955772, 'creator': 'john', 'planEffort': 600}>
Change work item states
-----------------------
>>> wi01.assign()
>>> wi01.state
'assigned'
>>> wi01.startWork(start=1229958000)
>>> wi01
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'running']:
{'created': ..., 'planEnd': 1229956372, 'start': 1229958000,
'assigned': ..., 'planDuration': 600, 'planStart': 1229955772,
'creator': 'john', 'planEffort': 600}>

View file

@ -449,12 +449,33 @@ class IWorkItem(Interface):
effort = Attribute('How much effort (time units) it took to finish the work.') effort = Attribute('How much effort (time units) it took to finish the work.')
# work item handling # work item handling
creator = Attribute('The party that has set up the work item.') creator = Attribute('The party that has set up the work item.')
created = Attribute('The timeStamp of the initial creation of the work item.')
assigned = Attribute('The timeStamp of the assignment of the work item.')
predecessor = Attribute('Optional: a work item this work item was created from.') predecessor = Attribute('Optional: a work item this work item was created from.')
continuation = Attribute('Optional: a work item that was created from this one ' continuation = Attribute('Optional: a work item that was created from this one '
'to continue the work.') 'to continue the work.')
newTask = Attribute('Optional: a new task that has been created based ' newTask = Attribute('Optional: a new task that has been created based '
'on this work item.') 'on this work item.')
def setInitData(**kw):
""" Set initial work item data (if allowed by the current state);
values not given will be derived if appropriate.
"""
def assign(party):
""" Assign the work item to the party given.
"""
def startWork(**kw):
""" Start working on the work item; properties may be given
as keyword arguments.
"""
def stopWork(transition='finish', **kw):
""" Finish working on the work item; properties may be given
as keyword arguments.
"""
class IWorkItems(Interface): class IWorkItems(Interface):
""" A collection (manager, container) of work items. """ A collection (manager, container) of work items.

View file

@ -25,13 +25,14 @@ $Id$
from zope import component from zope import component
from zope.component import adapts from zope.component import adapts
from zope.interface import implementer, implements from zope.interface import implementer, implements
from zope.traversing.api import getParent
from cybertools.organize.interfaces import IWorkItem, IWorkItems from cybertools.organize.interfaces import IWorkItem, IWorkItems
from cybertools.stateful.base import Stateful from cybertools.stateful.base import Stateful
from cybertools.stateful.definition import StatesDefinition from cybertools.stateful.definition import StatesDefinition
from cybertools.stateful.definition import State, Transition from cybertools.stateful.definition import State, Transition
from cybertools.stateful.interfaces import IStatesDefinition from cybertools.stateful.interfaces import IStatesDefinition
from cybertools.tracking.btree import Track from cybertools.tracking.btree import Track, getTimeStamp
from cybertools.tracking.interfaces import ITrackingStorage from cybertools.tracking.interfaces import ITrackingStorage
@ -62,8 +63,6 @@ class WorkItem(Stateful):
def getStatesDefinition(self): def getStatesDefinition(self):
return component.getUtility(IStatesDefinition, name=self.statesDefinition) return component.getUtility(IStatesDefinition, name=self.statesDefinition)
# work item attributes (except state that is provided by stateful
class WorkItemTrack(WorkItem, Track): class WorkItemTrack(WorkItem, Track):
""" A work item that may be stored as a track in a tracking storage. """ A work item that may be stored as a track in a tracking storage.
@ -73,21 +72,93 @@ class WorkItemTrack(WorkItem, Track):
index_attributes = metadata_attributes index_attributes = metadata_attributes
typeName = 'WorkItem' typeName = 'WorkItem'
initAttributes = set(['description', 'predecessor', initAttributes = set(['party', 'description', 'predecessor',
'planStart', 'planEnd', 'planDuration', 'planEffort']) 'planStart', 'planEnd', 'planDuration', 'planEffort'])
def __init__(self, taskId, runId, userName, data={}): def __init__(self, taskId, runId, userName, data={}):
for k in data: super(WorkItemTrack, self).__init__(taskId, runId, userName)
if k not in initAttributes:
raise ValueError("Illegal initial attribute: '%s'." % k)
super(WorkItemTrack, self).__init__(taskId, runId, userName, data)
self.state = self.getState() # make initial state persistent self.state = self.getState() # make initial state persistent
self.data['creator'] = userName
self.data['created'] = self.timeStamp
self.setInitData(**data)
def __getattr__(self, attr): def __getattr__(self, attr):
value = self.data.get(attr, _not_found) if attr not in IWorkItem:
if value is _not_found:
raise AttributeError(attr) raise AttributeError(attr)
return value return self.data.get(attr, None)
@property
def party(self):
return self.userName
def setInitData(self, **kw):
for k in kw:
if k not in self.initAttributes:
raise ValueError("Illegal initial attribute: '%s'." % k)
party = kw.pop('party', None)
if party is not None:
if self.state != 'created':
raise ValueError("Attribute 'party' may not be set in the state '%s'" %
self.state)
else:
self.userName = party
indexChanged = True
self.checkOverwrite(kw)
updatePlanData = False
indexChanged = False
data = self.data
for k, v in kw.items():
data[k] = v
if k.startswith('plan'):
updatePlanData = True
if self.planStart is not None and self.planStart != self.timeStamp:
self.timeStamp = self.planStart
indexChanged = True
if updatePlanData and self.planStart:
data['planEnd'], data['planDuration'], data['planEffort'] = \
recalcTimes(self.planStart, self.planEnd,
self.planDuration, self.planEffort)
if indexChanged:
self.reindex()
def assign(self, party=None):
self.doTransition('assign')
self.data['assigned'] = getTimeStamp()
if party is not None:
self.userName = party
self.reindex()
def startWork(self, **kw):
self.checkOverwrite(kw)
self.doTransition('start')
start = self.data['start'] = kw.pop('start', None) or getTimeStamp()
self.timeStamp = start
self.reindex()
def stopWork(self, transition='finish', **kw):
self.checkOverwrite(kw)
self.doTransition(transition)
self.reindex()
def reindex(self):
getParent(self).updateTrack(self, {}) # force reindex
def checkOverwrite(self, kw):
for k, v in kw.items():
#old = data.get(k)
old = getattr(self, k, None)
if old is not None and old != v:
raise ValueError("Attribute '%s' already set to '%s'." % (k, old))
def recalcTimes(start, end, duration, effort):
if duration is None and start and end:
duration = end - start
if end is None and start and duration:
end = start + duration
if effort is None and duration:
effort = duration
return end, duration, effort
class WorkItems(object): class WorkItems(object):

View file

@ -74,6 +74,8 @@ class Track(Persistent):
self.data = data self.data = data
def update(self, newData): def update(self, newData):
if not newData:
return
self.timeStamp = getTimeStamp() self.timeStamp = getTimeStamp()
data = self.data data = self.data
data.update(newData) data.update(newData)
@ -186,6 +188,8 @@ class TrackingStorage(BTreeContainer):
return self.updateTrack(track, data) return self.updateTrack(track, data)
trackId, trackNum = self.generateTrackId() trackId, trackNum = self.generateTrackId()
track = self.trackFactory(taskId, runId, userName, data) track = self.trackFactory(taskId, runId, userName, data)
track.__parent__ = self
track.__name__ = trackId
if timeStamp: if timeStamp:
track.timeStamp = timeStamp track.timeStamp = timeStamp
self[trackId] = track self[trackId] = track