work in progress: new work item lifecycle
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3136 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
cf93ec49d7
commit
2e3fd4f333
2 changed files with 113 additions and 32 deletions
|
@ -63,6 +63,7 @@ def workItemStates():
|
||||||
('plan', 'accept', 'start', 'stop', 'modify', 'close'),
|
('plan', 'accept', 'start', 'stop', 'modify', 'close'),
|
||||||
color='grey'),
|
color='grey'),
|
||||||
State('closed', 'closed', (), color='lightblue'),
|
State('closed', 'closed', (), color='lightblue'),
|
||||||
|
State('replaced', 'replaced', (), color='grey'),
|
||||||
Transition('plan', 'plan', 'planned'),
|
Transition('plan', 'plan', 'planned'),
|
||||||
Transition('accept', 'accept', 'accepted'),
|
Transition('accept', 'accept', 'accepted'),
|
||||||
Transition('start', 'start', 'running'),
|
Transition('start', 'start', 'running'),
|
||||||
|
@ -127,9 +128,12 @@ class WorkItem(Stateful, Track):
|
||||||
raise AttributeError(attr)
|
raise AttributeError(attr)
|
||||||
return self.data.get(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:
|
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()]:
|
if action not in [t.name for t in self.getAvailableTransitions()]:
|
||||||
raise ValueError("Action '%s' not allowed in state '%s'" %
|
raise ValueError("Action '%s' not allowed in state '%s'" %
|
||||||
(action, self.state))
|
(action, self.state))
|
||||||
|
@ -138,22 +142,36 @@ class WorkItem(Stateful, Track):
|
||||||
self.doTransition(action)
|
self.doTransition(action)
|
||||||
self.reindex('state')
|
self.reindex('state')
|
||||||
return self
|
return self
|
||||||
new = self.createNew(action, **kw)
|
new = self.createNew(action, userName, **kw)
|
||||||
|
if self.state == 'running':
|
||||||
|
new.replace(self)
|
||||||
new.doTransition(action)
|
new.doTransition(action)
|
||||||
new.reindex('state')
|
new.reindex('state')
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def modify(self, **kw):
|
def modify(self, userName, **kw):
|
||||||
print '*** modifying'
|
|
||||||
if self.state == 'new':
|
if self.state == 'new':
|
||||||
self.setData(**kw)
|
self.setData(**kw)
|
||||||
return self
|
return self
|
||||||
|
new = self.createNew('modify', userName, **kw)
|
||||||
|
new.replace(self, keepState=True)
|
||||||
|
return new
|
||||||
|
|
||||||
def delegate(self, **kw):
|
def delegate(self, userName, **kw):
|
||||||
print '*** delegating'
|
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):
|
def close(self, userName, **kw):
|
||||||
print '*** closing'
|
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)
|
specialActions = dict(modify=modify, delegate=delegate, close=close)
|
||||||
|
|
||||||
|
@ -172,22 +190,30 @@ class WorkItem(Stateful, Track):
|
||||||
for k, v in kw.items():
|
for k, v in kw.items():
|
||||||
data[k] = v
|
data[k] = v
|
||||||
|
|
||||||
def createNew(self, action, **kw):
|
def createNew(self, action, userName, copyData=True, **kw):
|
||||||
newData = {}
|
newData = {}
|
||||||
for k in self.initAttributes:
|
if copyData:
|
||||||
v = kw.get(k, _not_found)
|
for k in self.initAttributes:
|
||||||
if v is _not_found:
|
v = kw.get(k, _not_found)
|
||||||
if action == 'start' and k in ('end',):
|
if v is _not_found:
|
||||||
continue
|
if action == 'start' and k in ('end',):
|
||||||
if action in ('stop', 'finish') and k in ('duration', 'effort',):
|
continue
|
||||||
continue
|
if action in ('stop', 'finish') and k in ('duration', 'effort',):
|
||||||
v = self.data.get(k)
|
continue
|
||||||
if v is not None:
|
v = self.data.get(k)
|
||||||
newData[k] = v
|
if v is not None:
|
||||||
|
newData[k] = v
|
||||||
workItems = IWorkItems(getParent(self))
|
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
|
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):
|
def reindex(self, idx=None):
|
||||||
getParent(self).indexTrack(None, self, idx)
|
getParent(self).indexTrack(None, self, idx)
|
||||||
|
|
||||||
|
@ -217,10 +243,10 @@ class WorkItems(object):
|
||||||
criteria['runId'] = criteria.pop('run')
|
criteria['runId'] = criteria.pop('run')
|
||||||
return self.context.query(**criteria)
|
return self.context.query(**criteria)
|
||||||
|
|
||||||
def add(self, task, party, run=0, **kw):
|
def add(self, task, userName, run=0, **kw):
|
||||||
if not run:
|
if not run:
|
||||||
run = self.context.startRun()
|
run = self.context.startRun()
|
||||||
trackId = self.context.saveUserTrack(task, run, party, {})
|
trackId = self.context.saveUserTrack(task, run, userName, {})
|
||||||
track = self[trackId]
|
track = self[trackId]
|
||||||
track.setData(**kw)
|
track.setData(**kw)
|
||||||
return track
|
return track
|
||||||
|
|
|
@ -28,7 +28,7 @@ definition as a utility.
|
||||||
We are now ready to set up the tracking storage.
|
We are now ready to set up the tracking storage.
|
||||||
|
|
||||||
>>> tracks = TrackingStorage(trackFactory=WorkItem)
|
>>> 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,
|
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.
|
||||||
|
@ -68,7 +68,7 @@ the effort is taken from the duration if not set explicitly.
|
||||||
>>> wi01.effort
|
>>> wi01.effort
|
||||||
700
|
700
|
||||||
|
|
||||||
>>> w = wi01.doAction('plan', party='jim')
|
>>> w = wi01.doAction('plan', 'john', party='jim')
|
||||||
>>> wi01.userName
|
>>> wi01.userName
|
||||||
'jim'
|
'jim'
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ Change work item state
|
||||||
|
|
||||||
Now Jim accepts the work item, i.e. he wants to work on it.
|
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
|
>>> wi01.state
|
||||||
'planned'
|
'planned'
|
||||||
>>> wi02
|
>>> 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
|
Jim now really starts to work. The start time is usually set automatically
|
||||||
but may also be specified explicitly.
|
but may also be specified explicitly.
|
||||||
|
|
||||||
>>> wi03 = wi02.doAction('start', start=1229958000)
|
>>> wi03 = wi02.doAction('start', 'jim', start=1229958000)
|
||||||
>>> wi03
|
>>> wi03
|
||||||
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'running']:
|
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'running']:
|
||||||
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||||
|
@ -104,9 +104,14 @@ Stopping and finishing work
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
After five minutes of work Jim decides to stop working; but he will
|
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
|
||||||
|
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'replaced']:
|
||||||
|
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||||
>>> wi04
|
>>> wi04
|
||||||
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'stopped']:
|
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'stopped']:
|
||||||
{'start': 1229958000, 'created': ..., 'end': 1229958300, 'creator': 'jim'}>
|
{'start': 1229958000, 'created': ..., 'end': 1229958300, 'creator': 'jim'}>
|
||||||
|
@ -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
|
After another hour Jim works again on the task; he now finishes it within
|
||||||
ten minutes and records this in one step.
|
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
|
>>> wi05
|
||||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'finished']:
|
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'finished']:
|
||||||
{'start': 1229961600, 'created': ..., 'end': 1229967200, 'creator': 'jim'}>
|
{'start': 1229961600, 'created': ..., 'end': 1229962200, 'creator': 'jim'}>
|
||||||
|
>>> wi05.duration, wi05.effort
|
||||||
|
(600, 600)
|
||||||
|
|
||||||
Closing work
|
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
|
||||||
|
<WorkItem ['001', 1, 'john', '... ...', 'closed']:
|
||||||
|
{'created': ..., 'creator': 'john'}>
|
||||||
|
|
||||||
Let's now check how many work items have been generated.
|
Let's now check how many work items have been generated.
|
||||||
|
|
||||||
>>> len(list(workItems))
|
>>> len(list(workItems))
|
||||||
5
|
6
|
||||||
|
|
||||||
|
|
||||||
Delegation of Work Items
|
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
|
||||||
|
<WorkItem ['001', 2, 'john', '2008-12-22 18:33', 'planned']:
|
||||||
|
{'start': 1229970800, 'created': ..., 'creator': 'john'}>
|
||||||
|
>>> wi08
|
||||||
|
<WorkItem ['001', 2, 'annie', '2008-12-22 18:33', 'planned']:
|
||||||
|
{'start': 1229970800, 'created': ..., 'creator': 'john'}>
|
||||||
|
>>> len(list(workItems))
|
||||||
|
8
|
||||||
|
|
||||||
|
|
||||||
Modification of Work Items
|
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
|
||||||
|
<WorkItem ['001', 2, 'annie', '2008-12-22 18:33', 'replaced']:
|
||||||
|
{'start': 1229970800, 'created': ..., 'creator': 'john'}>
|
||||||
|
>>> wi09
|
||||||
|
<WorkItem ['001', 2, 'annie', '2008-12-22 18:33', 'planned']:
|
||||||
|
{'duration': 3600, 'start': 1229970800, 'created': ..., 'creator': 'annie'}>
|
||||||
|
|
||||||
|
|
||||||
Queries
|
Queries
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
Runs
|
||||||
|
----
|
||||||
|
|
||||||
|
>>> list(tracks.runs)
|
||||||
|
[2]
|
||||||
|
>>> list(tracks.finishedRuns)
|
||||||
|
[1]
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue