work in progress: new work item lifecycle
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3133 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
b8997e7184
commit
8bf7ef1ea0
5 changed files with 240 additions and 265 deletions
|
@ -64,110 +64,3 @@ Service Management
|
|||
|
||||
(See cyberapps.tumsm for comprehensive description and tests.)
|
||||
|
||||
|
||||
Work
|
||||
====
|
||||
|
||||
Work items are stored in a tracking storage; in order to conveniently access
|
||||
the work items we have to provide an adapter to the tracking storage.
|
||||
|
||||
>>> from cybertools.tracking.btree import TrackingStorage
|
||||
>>> from cybertools.organize.interfaces import IWorkItems
|
||||
>>> from cybertools.organize.work import WorkItem, WorkItems
|
||||
>>> component.provideAdapter(WorkItems)
|
||||
|
||||
The individual work item (a track) is carrying a state attribute that is
|
||||
governed by a special states definition. We have to register this states
|
||||
definition as a utility.
|
||||
|
||||
>>> from cybertools.organize.work import workItemStates
|
||||
>>> component.provideUtility(workItemStates(), name='organize.workItemStates')
|
||||
|
||||
We are now ready to set up the tracking storage.
|
||||
|
||||
>>> tracks = TrackingStorage(trackFactory=WorkItem)
|
||||
>>> workItems = component.getAdapter(tracks, IWorkItems)
|
||||
|
||||
The work management only deals with the IDs or names of tasks and persons,
|
||||
so we do not have to set up real objects.
|
||||
|
||||
>>> wi01 = workItems.add('001', 'john')
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'john', '...', 'new']:
|
||||
{'created': ..., 'creator': 'john'}>
|
||||
|
||||
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
|
||||
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='annie')
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'annie', '2008-12-22 14:22', 'new']:
|
||||
{'created': ..., 'planEnd': 1229956372, 'planDuration': 600,
|
||||
'planStart': 1229955772, 'creator': 'john', 'planEffort': 600}>
|
||||
|
||||
It's possible to change a value after it has been set as long as the work
|
||||
item is in state 'new'.
|
||||
|
||||
>>> wi01.setInitData(planEffort=700)
|
||||
>>> wi01.planEffort
|
||||
700
|
||||
|
||||
>>> wi01.setInitData(party='jim')
|
||||
>>> wi01.userName
|
||||
'jim'
|
||||
|
||||
Change work item state
|
||||
----------------------
|
||||
|
||||
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' already set to 'jim'.
|
||||
|
||||
Jim now really starts to work. The start time is usually set automatically
|
||||
but may also be specified explicitly.
|
||||
|
||||
>>> 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': 700}>
|
||||
|
||||
Stopping work
|
||||
-------------
|
||||
|
||||
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.
|
||||
|
||||
Note that the work item has already been set to assigned as Jim has
|
||||
committed himself to continue working on it by selecting the ``continue``
|
||||
transition.
|
||||
|
||||
>>> wi02 = wi01.stopWork('continue', end=1229958300, planStart=1229960000,
|
||||
... planDuration=400, planEnd=None, planEffort=None)
|
||||
>>> wi02
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 15:33', 'assigned']:
|
||||
{'predecessor': '0000001', 'created': ..., 'planEnd': 1229960400,
|
||||
'assigned': ..., 'planDuration': 400, 'planStart': 1229960000,
|
||||
'creator': 'jim', 'planEffort': 400}>
|
||||
|
|
|
@ -426,6 +426,7 @@ class IWorkItem(ITrack):
|
|||
done by exactly one party (usually a person).
|
||||
"""
|
||||
|
||||
# index attributes
|
||||
task = Attribute('The task this work item belongs to, identified by '
|
||||
'its name or ID.')
|
||||
run = Attribute('Used for recurring tasks: identifies the run '
|
||||
|
@ -433,17 +434,9 @@ class IWorkItem(ITrack):
|
|||
party = Attribute('Whoever does the work, usually a person, identified '
|
||||
'by its name or ID.')
|
||||
state = Attribute('The current state the work item is in.')
|
||||
# standard attributes
|
||||
title = Attribute('A short text characterizing the work item.')
|
||||
description = Attribute('A note about what has to be done, and why...')
|
||||
# optional plan fields; duration (and effort) may be derived from start and end
|
||||
# all date/time fields are timeStamp values, all duration and effort
|
||||
# fields are in seconds
|
||||
planStart = Attribute('When the work should start.')
|
||||
planEnd = Attribute('When the work should be finished.')
|
||||
planDuration = Attribute('How long it may take to finish the work.')
|
||||
planEffort = Attribute('How much effort (time units) it might take '
|
||||
'to finish the work.')
|
||||
# real stuff
|
||||
start = Attribute('When the work was started.')
|
||||
end = Attribute('When the work was finished.')
|
||||
duration = Attribute('How long it took to finish the work.')
|
||||
|
@ -452,32 +445,14 @@ class IWorkItem(ITrack):
|
|||
# work item handling
|
||||
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.')
|
||||
successor = Attribute('Optional: a work item that was created from this one '
|
||||
'to continue the work.')
|
||||
newTask = Attribute('Optional: a new task that has been created based '
|
||||
'on this work item.')
|
||||
|
||||
def setInitData(**kw):
|
||||
def setData(**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.
|
||||
"""
|
||||
|
||||
def doAction(action, **kw):
|
||||
""" Execute an action.
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ def test_suite():
|
|||
return unittest.TestSuite((
|
||||
unittest.makeSuite(TestParty),
|
||||
DocFileSuite('README.txt', optionflags=flags),
|
||||
DocFileSuite('work.txt', optionflags=flags),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
231
organize/work.py
231
organize/work.py
|
@ -41,22 +41,37 @@ _not_found = object()
|
|||
@implementer(IStatesDefinition)
|
||||
def workItemStates():
|
||||
return StatesDefinition('workItemStates',
|
||||
State('new', 'new', ('assign', 'cancel',), color='red'),
|
||||
State('assigned', 'assigned', ('start', 'finish', 'cancel', 'transfer'),
|
||||
State('new', 'new',
|
||||
('plan', 'accept', 'start', 'stop', 'finish', 'modify', 'delegate'),
|
||||
color='red'),
|
||||
State('planned', 'planned',
|
||||
('plan', 'accept', 'start', 'stop', 'finish', 'cancel', 'modify'),
|
||||
color='red'),
|
||||
State('accepted', 'accepted',
|
||||
('plan', 'accept', 'start', 'stop', 'finish', 'cancel', 'modify'),
|
||||
color='yellow'),
|
||||
State('running', 'running', ('finish', 'continue', 'cancel', 'transfer'),
|
||||
State('running', 'running',
|
||||
('stop', 'finish', 'cancel', 'modify'),
|
||||
color='orange'),
|
||||
State('finished', 'finished', ('cancel',), color='green'),
|
||||
State('continued', 'continued', ('finish', 'cancel'), color='blue'),
|
||||
State('transferred', 'transferred', ('finish', 'cancel'),
|
||||
color='lightblue'),
|
||||
State('cancelled', 'cancelled', (), color='grey'),
|
||||
Transition('assign', 'assign', 'assigned'),
|
||||
State('stopped', 'stopped',
|
||||
('plan', 'accept', 'start', 'stop', 'finish', 'cancel', 'modify'),
|
||||
color='orange'),
|
||||
State('finished', 'finished',
|
||||
('plan', 'accept', 'start', 'stop', 'modify', 'close'),
|
||||
color='green'),
|
||||
State('cancelled', 'cancelled',
|
||||
('plan', 'accept', 'start', 'stop', 'modify', 'close'),
|
||||
color='grey'),
|
||||
State('closed', 'closed', (), color='lightblue'),
|
||||
Transition('plan', 'plan', 'planned'),
|
||||
Transition('accept', 'accept', 'accepted'),
|
||||
Transition('start', 'start', 'running'),
|
||||
Transition('stop', 'stop', 'stopped'),
|
||||
Transition('finish', 'finish', 'finished'),
|
||||
Transition('continue', 'continue', 'continued'),
|
||||
Transition('transfer', 'transfer', 'transferred'),
|
||||
Transition('cancel', 'cancel', 'cancelled'),
|
||||
Transition('modify', 'modify', 'new'),
|
||||
Transition('delegate', 'delegate', 'planned'),
|
||||
Transition('close', 'close', 'closed'),
|
||||
initialState='new')
|
||||
|
||||
|
||||
|
@ -72,10 +87,8 @@ class WorkItem(Stateful, Track):
|
|||
index_attributes = metadata_attributes
|
||||
typeName = 'WorkItem'
|
||||
|
||||
initAttributes = set(['party', 'title', 'description', 'predecessor',
|
||||
'planStart', 'planEnd', 'planDuration', 'planEffort'])
|
||||
|
||||
closeAttributes = set(['end', 'duration', 'effort', 'comment'])
|
||||
initAttributes = set(['party', 'title', 'description', 'start', 'end',
|
||||
'duration', 'effort'])
|
||||
|
||||
def __init__(self, taskId, runId, userName, data):
|
||||
super(WorkItem, self).__init__(taskId, runId, userName, data)
|
||||
|
@ -83,11 +96,6 @@ class WorkItem(Stateful, Track):
|
|||
self.data['creator'] = userName
|
||||
self.data['created'] = self.timeStamp
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr not in IWorkItem:
|
||||
raise AttributeError(attr)
|
||||
return self.data.get(attr)
|
||||
|
||||
def getStatesDefinition(self):
|
||||
return component.getUtility(IStatesDefinition, name=self.statesDefinition)
|
||||
|
||||
|
@ -99,124 +107,85 @@ class WorkItem(Stateful, Track):
|
|||
def title(self):
|
||||
return self.data.get('title') or self.description
|
||||
|
||||
def setInitData(self, **kw):
|
||||
indexChanged = False
|
||||
updatePlanData = False
|
||||
for k in kw:
|
||||
if k not in self.initAttributes:
|
||||
raise ValueError("Illegal initial attribute: '%s'." % k)
|
||||
self.checkOverwrite(kw)
|
||||
@property
|
||||
def duration(self):
|
||||
value = self.data.get('duration')
|
||||
if value is None:
|
||||
start, end = (self.data.get('start'), self.data.get('end'))
|
||||
if not None in (start, end):
|
||||
value = end - start
|
||||
return value
|
||||
|
||||
@property
|
||||
def effort(self):
|
||||
value = self.data.get('effort')
|
||||
if value is None:
|
||||
return self.duration
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr not in IWorkItem:
|
||||
raise AttributeError(attr)
|
||||
return self.data.get(attr)
|
||||
|
||||
def doAction(self, action, **kw):
|
||||
if action in self.specialActions:
|
||||
return self.specialActions[action](self, **kw)
|
||||
if action not in [t.name for t in self.getAvailableTransitions()]:
|
||||
raise ValueError("Action '%s' not allowed in state '%s'" %
|
||||
(action, self.state))
|
||||
if self.state == 'new':
|
||||
self.setData(**kw)
|
||||
self.doTransition(action)
|
||||
self.reindex('state')
|
||||
return self
|
||||
new = self.createNew(action, **kw)
|
||||
new.doTransition(action)
|
||||
new.reindex('state')
|
||||
return new
|
||||
|
||||
def modify(self, **kw):
|
||||
print '*** modifying'
|
||||
if self.state == 'new':
|
||||
self.setData(**kw)
|
||||
return self
|
||||
|
||||
def delegate(self, **kw):
|
||||
print '*** delegating'
|
||||
|
||||
def close(self, **kw):
|
||||
print '*** closing'
|
||||
|
||||
specialActions = dict(modify=modify, delegate=delegate, close=close)
|
||||
|
||||
def setData(self, **kw):
|
||||
if self.state != 'new':
|
||||
raise ValueError("Attributes may only be changed in state 'new'.")
|
||||
party = kw.pop('party', None)
|
||||
if party is not None:
|
||||
self.userName = party
|
||||
indexChanged = True
|
||||
self.reindex('userName')
|
||||
start = kw.get('start')
|
||||
if start is not None:
|
||||
self.timeStamp = start
|
||||
self.reindex('timeStamp')
|
||||
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.doTransition(transition)
|
||||
data = self.data
|
||||
for k in self.closeAttributes:
|
||||
v = kw.pop(k, None)
|
||||
def createNew(self, action, **kw):
|
||||
newData = {}
|
||||
for k in self.initAttributes:
|
||||
v = kw.get(k, _not_found)
|
||||
if v is _not_found:
|
||||
v = self.data.get(k)
|
||||
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))
|
||||
new = workItems.add(self.taskId, self.userName, self.runId, **newData)
|
||||
if transition == 'continue':
|
||||
new.assign()
|
||||
#new.data['predecessor'] = getName(self)
|
||||
new.data['predecessor'] = self.__name__
|
||||
#self.data['successor'] = getName(new)
|
||||
self.data['successor'] = new.__name__
|
||||
return new
|
||||
newData[k] = v
|
||||
workItems = IWorkItems(getParent(self))
|
||||
new = workItems.add(self.taskId, self.userName, self.runId, **newData)
|
||||
return new
|
||||
|
||||
# actions
|
||||
|
||||
def doAction(self, action, **kw):
|
||||
# TODO: check if action is allowed
|
||||
m = getattr(self, 'action_' + action)
|
||||
m(**kw)
|
||||
|
||||
def action_start(self, **kw):
|
||||
if self.state == 'new':
|
||||
self.assign(kw.pop('party', None))
|
||||
self.startWork(**kw)
|
||||
|
||||
def action_finish(self, **kw):
|
||||
for k in ('description', 'title'):
|
||||
if k in kw:
|
||||
self.data[k] = kw.pop(k)
|
||||
if self.state == 'new':
|
||||
self.assign(kw.pop('party', None))
|
||||
if self.state == 'assigned':
|
||||
self.startWork(start=kw.pop('start', None))
|
||||
self.stopWork(**kw)
|
||||
|
||||
# auxiliary methods
|
||||
|
||||
def reindex(self):
|
||||
getParent(self).updateTrack(self, {}) # force reindex
|
||||
|
||||
def checkOverwrite(self, kw):
|
||||
if self.state == 'new':
|
||||
return
|
||||
for k, v in kw.items():
|
||||
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
|
||||
def reindex(self, idx=None):
|
||||
getParent(self).indexTrack(None, self, idx)
|
||||
|
||||
|
||||
class WorkItems(object):
|
||||
|
@ -245,7 +214,9 @@ class WorkItems(object):
|
|||
return self.context.query(**criteria)
|
||||
|
||||
def add(self, task, party, run=0, **kw):
|
||||
if not run:
|
||||
run = self.context.startRun()
|
||||
trackId = self.context.saveUserTrack(task, run, party, {})
|
||||
track = self[trackId]
|
||||
track.setInitData(**kw)
|
||||
track.setData(**kw)
|
||||
return track
|
||||
|
|
135
organize/work.txt
Normal file
135
organize/work.txt
Normal file
|
@ -0,0 +1,135 @@
|
|||
==========================================================
|
||||
Organizations: Persons, Institutions, Addresses, Work, ...
|
||||
==========================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
>>> from zope import component
|
||||
|
||||
|
||||
Basic Work Item Lifecycle
|
||||
=========================
|
||||
|
||||
Work items are stored in a tracking storage; in order to conveniently access
|
||||
the work items we have to provide an adapter to the tracking storage.
|
||||
|
||||
>>> from cybertools.tracking.btree import TrackingStorage
|
||||
>>> from cybertools.organize.interfaces import IWorkItems
|
||||
>>> from cybertools.organize.work import WorkItem, WorkItems
|
||||
>>> component.provideAdapter(WorkItems)
|
||||
|
||||
The individual work item (a track) is carrying a state attribute that is
|
||||
governed by a special states definition. We have to register this states
|
||||
definition as a utility.
|
||||
|
||||
>>> from cybertools.organize.work import workItemStates
|
||||
>>> component.provideUtility(workItemStates(), name='organize.workItemStates')
|
||||
|
||||
We are now ready to set up the tracking storage.
|
||||
|
||||
>>> tracks = TrackingStorage(trackFactory=WorkItem)
|
||||
>>> workItems = component.getAdapter(tracks, IWorkItems)
|
||||
|
||||
The work management only deals with the IDs or names of tasks and persons,
|
||||
so we do not have to set up real objects.
|
||||
|
||||
>>> wi01 = workItems.add('001', 'john')
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'john', '...', 'new']:
|
||||
{'created': ..., 'creator': 'john'}>
|
||||
|
||||
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
|
||||
True
|
||||
>>> wi01.something
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: something
|
||||
|
||||
Properties may be set as long as the work item is in status "new".
|
||||
|
||||
>>> wi01.setData(start=1229955772, party='annie')
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'annie', '2008-12-22 14:22', 'new']:
|
||||
{'start': 1229955772, 'created': ..., 'creator': 'john'}>
|
||||
|
||||
The duration value is calculated automatically when start and end are set;
|
||||
the effort is taken from the duration if not set explicitly.
|
||||
|
||||
>>> (wi01.end, wi01.duration, wi01.effort)
|
||||
(None, None, None)
|
||||
>>> wi01.setData(end=1229956372)
|
||||
>>> (wi01.end, wi01.duration, wi01.effort)
|
||||
(1229956372, 600, 600)
|
||||
|
||||
>>> wi01.setData(duration=700)
|
||||
>>> wi01.effort
|
||||
700
|
||||
|
||||
>>> w = wi01.doAction('plan', party='jim')
|
||||
>>> wi01.userName
|
||||
'jim'
|
||||
|
||||
Change work item state
|
||||
----------------------
|
||||
|
||||
Now Jim accepts the work item, i.e. he wants to work on it.
|
||||
|
||||
>>> wi02 = wi01.doAction('accept')
|
||||
>>> wi01.state
|
||||
'planned'
|
||||
>>> wi02
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 14:22', 'accepted']:
|
||||
{'duration': 700, 'start': 1229955772, 'created': ...,
|
||||
'end': 1229956372, 'creator': 'jim'}>
|
||||
|
||||
It is not possible to change a value of a work item that is not in state "new".
|
||||
|
||||
>>> wi01.setData(party='annie')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Attributes may only be changed 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
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'running']:
|
||||
{'duration': 700, 'start': 1229958000, 'created': ...,
|
||||
'end': 1229956372, 'creator': 'jim'}>
|
||||
|
||||
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.
|
||||
|
||||
>>> wi04 = wi03.doAction('stop', end=1229958300)
|
||||
>>> wi04
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'stopped']:
|
||||
{'duration': 700, 'start': 1229958000, 'created': ...,
|
||||
'end': 1229958300, 'creator': 'jim'}>
|
||||
|
||||
Closing work
|
||||
------------
|
||||
|
||||
Let's now check how many work items have been generated.
|
||||
|
||||
>>> len(list(workItems))
|
||||
4
|
||||
|
||||
|
||||
Delegation of Work Items
|
||||
========================
|
||||
|
||||
|
||||
Modification of Work Items
|
||||
==========================
|
||||
|
||||
|
||||
Queries
|
||||
=======
|
||||
|
Loading…
Add table
Reference in a new issue