From 2e3fd4f3339d9b74aedc282cf264e92781b8b580 Mon Sep 17 00:00:00 2001 From: helmutm Date: Sun, 11 Jan 2009 14:56:12 +0000 Subject: [PATCH] work in progress: new work item lifecycle git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3136 fd906abe-77d9-0310-91a1-e0d9ade77398 --- organize/work.py | 72 +++++++++++++++++++++++++++++++--------------- organize/work.txt | 73 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 113 insertions(+), 32 deletions(-) diff --git a/organize/work.py b/organize/work.py index d5c0be8..d162098 100644 --- a/organize/work.py +++ b/organize/work.py @@ -63,6 +63,7 @@ def workItemStates(): ('plan', 'accept', 'start', 'stop', 'modify', 'close'), color='grey'), State('closed', 'closed', (), color='lightblue'), + State('replaced', 'replaced', (), color='grey'), Transition('plan', 'plan', 'planned'), Transition('accept', 'accept', 'accepted'), Transition('start', 'start', 'running'), @@ -127,9 +128,12 @@ class WorkItem(Stateful, Track): raise AttributeError(attr) return self.data.get(attr) - def doAction(self, action, **kw): + def doAction(self, action, userName, **kw): + currentWorkItems = list(getParent(self).query(runId=self.runId)) + if self != currentWorkItems[-1]: + raise ValueError("Actions are only allowed on the last item of a run.") if action in self.specialActions: - return self.specialActions[action](self, **kw) + return self.specialActions[action](self, userName, **kw) if action not in [t.name for t in self.getAvailableTransitions()]: raise ValueError("Action '%s' not allowed in state '%s'" % (action, self.state)) @@ -138,22 +142,36 @@ class WorkItem(Stateful, Track): self.doTransition(action) self.reindex('state') return self - new = self.createNew(action, **kw) + new = self.createNew(action, userName, **kw) + if self.state == 'running': + new.replace(self) new.doTransition(action) new.reindex('state') return new - def modify(self, **kw): - print '*** modifying' + def modify(self, userName, **kw): if self.state == 'new': self.setData(**kw) return self + new = self.createNew('modify', userName, **kw) + new.replace(self, keepState=True) + return new - def delegate(self, **kw): - print '*** delegating' + def delegate(self, userName, **kw): + new = self.createNew('delegate', userName, **kw) + new.doTransition('plan') + new.reindex('state') + if self.state == 'new': + self.doTransition('plan') + self.reindex('state') + return new - def close(self, **kw): - print '*** closing' + def close(self, userName, **kw): + new = self.createNew('close', userName, copyData=False, **kw) + new.state = 'closed' + new.reindex('state') + getParent(self).stopRun(runId=self.runId, finish=True) + return new specialActions = dict(modify=modify, delegate=delegate, close=close) @@ -172,22 +190,30 @@ class WorkItem(Stateful, Track): for k, v in kw.items(): data[k] = v - def createNew(self, action, **kw): + def createNew(self, action, userName, copyData=True, **kw): newData = {} - for k in self.initAttributes: - v = kw.get(k, _not_found) - if v is _not_found: - if action == 'start' and k in ('end',): - continue - if action in ('stop', 'finish') and k in ('duration', 'effort',): - continue - v = self.data.get(k) - if v is not None: - newData[k] = v + if copyData: + for k in self.initAttributes: + v = kw.get(k, _not_found) + if v is _not_found: + if action == 'start' and k in ('end',): + continue + if action in ('stop', 'finish') and k in ('duration', 'effort',): + continue + v = self.data.get(k) + if v is not None: + newData[k] = v workItems = IWorkItems(getParent(self)) - new = workItems.add(self.taskId, self.userName, self.runId, **newData) + new = workItems.add(self.taskId, userName, self.runId, **newData) return new + def replace(self, other, keepState=False): + if keepState: + self.state = other.state + self.reindex('state') + other.state = 'replaced' + other.reindex('state') + def reindex(self, idx=None): getParent(self).indexTrack(None, self, idx) @@ -217,10 +243,10 @@ class WorkItems(object): criteria['runId'] = criteria.pop('run') return self.context.query(**criteria) - def add(self, task, party, run=0, **kw): + def add(self, task, userName, run=0, **kw): if not run: run = self.context.startRun() - trackId = self.context.saveUserTrack(task, run, party, {}) + trackId = self.context.saveUserTrack(task, run, userName, {}) track = self[trackId] track.setData(**kw) return track diff --git a/organize/work.txt b/organize/work.txt index 5b2ba02..fd4ab92 100644 --- a/organize/work.txt +++ b/organize/work.txt @@ -28,7 +28,7 @@ definition as a utility. We are now ready to set up the tracking storage. >>> tracks = TrackingStorage(trackFactory=WorkItem) - >>> workItems = component.getAdapter(tracks, IWorkItems) + >>> workItems = IWorkItems(tracks) The work management only deals with the IDs or names of tasks and persons, so we do not have to set up real objects. @@ -68,7 +68,7 @@ the effort is taken from the duration if not set explicitly. >>> wi01.effort 700 - >>> w = wi01.doAction('plan', party='jim') + >>> w = wi01.doAction('plan', 'john', party='jim') >>> wi01.userName 'jim' @@ -77,7 +77,7 @@ Change work item state Now Jim accepts the work item, i.e. he wants to work on it. - >>> wi02 = wi01.doAction('accept') + >>> wi02 = wi01.doAction('accept', 'jim') >>> wi01.state 'planned' >>> wi02 @@ -95,7 +95,7 @@ It is not possible to change a value of a work item that is not in state "new". Jim now really starts to work. The start time is usually set automatically but may also be specified explicitly. - >>> wi03 = wi02.doAction('start', start=1229958000) + >>> wi03 = wi02.doAction('start', 'jim', start=1229958000) >>> wi03 @@ -104,9 +104,14 @@ Stopping and finishing work --------------------------- After five minutes of work Jim decides to stop working; but he will -continue work later, so he executes a ``stop`` action. +continue work later, so he executes a ``stop`` action. The work item marked +as "running" will be replaced by a new one. - >>> wi04 = wi03.doAction('stop', end=1229958300) + >>> wi04 = wi03.doAction('stop', 'jim', end=1229958300) + + >>> wi03 + >>> wi04 @@ -114,28 +119,78 @@ continue work later, so he executes a ``stop`` action. After another hour Jim works again on the task; he now finishes it within ten minutes and records this in one step. - >>> wi05 = wi04.doAction('finish', start=1229961600, end=1229967200) + >>> wi05 = wi04.doAction('finish', 'jim', start=1229961600, end=1229962200) >>> wi05 + {'start': 1229961600, 'created': ..., 'end': 1229962200, 'creator': 'jim'}> + >>> wi05.duration, wi05.effort + (600, 600) Closing work ------------ +As the work is now finished, the work item may be closed; the corresponding +"run" (a sequence of work items belonging together) will be finished. + + >>> wi06 = wi05.doAction('close', 'john') + >>> wi06 + + Let's now check how many work items have been generated. >>> len(list(workItems)) - 5 + 6 Delegation of Work Items ======================== +A user may delegate a newly created work item to another party. This +will create a new work item even if the initial one is still in state "new". +Both work items are now in "planned" state. + + >>> wi07 = workItems.add('001', 'john', start=1229970800) + >>> wi08 = wi07.doAction('delegate', 'john', party='annie') + + >>> wi07 + + >>> wi08 + + >>> len(list(workItems)) + 8 + Modification of Work Items ========================== +Existing work items may never be modified except when still in "new" state. +To allow for correcting errors that may be detected later there is a "modify" +action that will in fact create a new work item. This makes sure that +all changes will be tracked correctly. + +Note that nevertheless only the last work item of a run may be modified. + + >>> wi09 = wi08.doAction('modify', 'annie', duration=3600) + + >>> wi08 + + >>> wi09 + + Queries ======= +Runs +---- + + >>> list(tracks.runs) + [2] + >>> list(tracks.finishedRuns) + [1] +