From e7c3f8ac5608aa7491b95a48c79e7bf2a5fed62c Mon Sep 17 00:00:00 2001 From: helmutm Date: Thu, 25 Dec 2008 12:11:13 +0000 Subject: [PATCH] work in progress: manage work items git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3083 fd906abe-77d9-0310-91a1-e0d9ade77398 --- organize/README.txt | 46 ++++++++++++++++++++++++++++++++++++++--- organize/work.py | 50 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/organize/README.txt b/organize/README.txt index d40f3a4..0e179ab 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -96,7 +96,7 @@ so we do not have to set up real objects. -Properties that have not been set explicitly default to None; properties +Properties that have not been set explicitly have a default of None; properties not specified in the IWorkItem interface will lead to an AttributeError. >>> wi01.description is None @@ -108,18 +108,43 @@ not specified in the IWorkItem interface will lead to an AttributeError. Certain (not all) properties may be set after creation. - >>> wi01.setInitData(planStart=1229955772, planDuration=600, party='jim') + >>> wi01.setInitData(planStart=1229955772, planDuration=600, party='annie') >>> wi01 - +It's not possible to change a value after it has been set, even if it is +set via an automatic calculation like for the ``planEffort`` field. + + >>> wi01.setInitData(planEffort=400) + Traceback (most recent call last): + ... + ValueError: Attribute 'planEffort' already set to '600'. + +There is one exception to this rule: The party may be changed as long as +the work item is not in the ``assigned`` state. + + >>> wi01.setInitData(party='jim') + >>> wi01.userName + 'jim' + Change work item states ----------------------- +Now Jim accepts the work item, i.e. he wants to work on it. Now the party +that the work item is assigned to may not be changed any more. + >>> wi01.assign() >>> wi01.state 'assigned' + >>> wi01.setInitData(party='annie') + Traceback (most recent call last): + ... + ValueError: Attribute 'party' may not be set in state 'assigned'. + +Jim now really starts to work. The start time is usually set automatically +but may also be specified explicitly. >>> wi01.startWork(start=1229958000) >>> wi01 @@ -127,3 +152,18 @@ Change work item states {'created': ..., 'planEnd': 1229956372, 'start': 1229958000, 'assigned': ..., 'planDuration': 600, 'planStart': 1229955772, 'creator': 'john', 'planEffort': 600}> + +After five minutes of work Jim decides to stop working; but he will +continue work later, so he executes a ``continue`` transition that will +set up a copy of the work item. + +He also specifies a new plan start and duration for the new work item. +Plan end and plan effort are given explicitly as None values so that they +won't be taken from the old work item but recalculated. + + >>> wi02 = wi01.stopWork('continue', end=1229958300, planStart=1229960000, + ... planDuration=400, planEnd=None, planEffort=None) + >>> wi02 + diff --git a/organize/work.py b/organize/work.py index a898e2c..b771d8d 100644 --- a/organize/work.py +++ b/organize/work.py @@ -35,6 +35,8 @@ from cybertools.stateful.interfaces import IStatesDefinition from cybertools.tracking.btree import Track, getTimeStamp from cybertools.tracking.interfaces import ITrackingStorage +_not_found = object() + @implementer(IStatesDefinition) def workItemStates(): @@ -42,13 +44,16 @@ def workItemStates(): State('created', 'created', ('assign', 'cancel',), color='red'), State('assigned', 'assigned', ('start', 'finish', 'cancel', 'transfer'), color='yellow'), - State('running', 'running', ('finish',), color='green'), - State('finished', 'finished', (), color='blue'), - State('transferred', 'transferred', (), color='grey'), + State('running', 'running', ('finish', 'continue', 'cancel', 'transfer'), + color='orange'), + State('finished', 'finished', (), color='green'), + State('continued', 'continued', (), color='blue'), + State('transferred', 'transferred', (), color='lightblue'), State('cancelled', 'cancelled', (), color='grey'), Transition('assign', 'assign', 'assigned'), Transition('start', 'start', 'running'), Transition('finish', 'finish', 'finished'), + Transition('continue', 'continue', 'continued'), Transition('transfer', 'transfer', 'transferred'), Transition('cancel', 'cancel', 'cancelled'), initialState='created') @@ -75,12 +80,13 @@ class WorkItemTrack(WorkItem, Track): initAttributes = set(['party', 'description', 'predecessor', 'planStart', 'planEnd', 'planDuration', 'planEffort']) - def __init__(self, taskId, runId, userName, data={}): - super(WorkItemTrack, self).__init__(taskId, runId, userName) + closeAttributes = set(['end', 'duration', 'effort', 'comment']) + + def __init__(self, taskId, runId, userName, data): + super(WorkItemTrack, self).__init__(taskId, runId, userName, data) self.state = self.getState() # make initial state persistent self.data['creator'] = userName self.data['created'] = self.timeStamp - self.setInitData(**data) def __getattr__(self, attr): if attr not in IWorkItem: @@ -98,7 +104,7 @@ class WorkItemTrack(WorkItem, Track): 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'" % + raise ValueError("Attribute 'party' may not be set in state '%s'." % self.state) else: self.userName = party @@ -136,9 +142,31 @@ class WorkItemTrack(WorkItem, Track): self.reindex() def stopWork(self, transition='finish', **kw): - self.checkOverwrite(kw) self.doTransition(transition) + data = self.data + for k in self.closeAttributes: + v = kw.pop(k, None) + if v is not None: + data[k] = v + if self.start: + data['end'], data['duration'], data['effort'] = \ + recalcTimes(self.start, self.end, self.duration, self.effort) + self.timeStamp = self.end or getTimeStamp() self.reindex() + if transition in ('continue', 'transfer'): + if transition == 'continue' and kw.get('party') is not None: + raise ValueError("Setting 'party' is not allowed when continuing.") + newData = {} + for k in self.initAttributes: + v = kw.pop(k, _not_found) + if v is _not_found: + v = data.get(k) + if v is not None: + newData[k] = v + if transition == 'transfer' and 'party' not in newData: + raise ValueError("Property 'party' must be set when transferring.") + workItems = IWorkItems(getParent(self)) + return workItems.add(self.taskId, self.userName, self.runId, **newData) def reindex(self): getParent(self).updateTrack(self, {}) # force reindex @@ -175,5 +203,7 @@ class WorkItems(object): return self.context[key] def add(self, task, party, run=0, **kw): - trackId = self.context.saveUserTrack(task, run, party, kw) - return self[trackId] + trackId = self.context.saveUserTrack(task, run, party, {}) + track = self[trackId] + track.setInitData(**kw) + return track