==========================================================
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 = 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.

  >>> 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 15: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', 'john', party='jim')
  >>> wi01.userName
  'jim'

Change work item state
----------------------

Now Jim accepts the work item, i.e. he wants to work on it. The previous
record keeps its state but it is marked by a "_x" suffix so that it may
be excluded from queries.

  >>> wi02 = wi01.doAction('accept', 'jim')
  >>> wi01.state
  'planned_x'
  >>> wi02
  <WorkItem ['001', 1, 'jim', '2008-12-22 15: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', 'jim', start=1229958000)
  >>> wi03
  <WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'running']:
   {'duration': 0, 'start': 1229958000, 'created': ..., '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. The work item marked
as "running" will be replaced by a new one.

  >>> wi04 = wi03.doAction('work', 'jim', end=1229958300)

  >>> wi03
  <WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'replaced']:
   {'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
  >>> wi04
  <WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'done']:
   {'start': 1229958000, 'created': ..., 'end': 1229958300, 'creator': 'jim'}>

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', 'jim', start=1229961600, end=1229962200)
  >>> wi05
  <WorkItem ['001', 1, 'jim', '2008-12-22 17:00', 'finished']:
   {'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
  <WorkItem ['001', 1, 'jim', '... ...', 'closed']:
   {'start': ..., 'created': ..., 'end': ..., 'creator': 'john'}>

Let's now check how many work items have been generated.

  >>> len(list(workItems))
  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".
The new work item is now in "planned" state, its predecessor is market as
delegated so that it may be selected by queries.

  >>> wi07 = workItems.add('001', 'john', start=1229970800)
  >>> wi08 = wi07.doAction('delegate', 'john', party='annie')

  >>> wi07
  <WorkItem ['001', 2, 'john', '2008-12-22 19:33', 'delegated']:
   {'start': 1229970800, 'created': ..., 'creator': 'john'}>
  >>> wi08
  <WorkItem ['001', 3, 'annie', '2008-12-22 19:33', 'planned']:
   {'start': 1229970800, 'created': ..., 'source': '0000007', 'creator': 'john'}>
  >>> 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
  <WorkItem ['001', 3, 'annie', '2008-12-22 19:33', 'replaced']:
   {'start': 1229970800, 'created': ..., 'creator': 'john'}>
  >>> wi09
  <WorkItem ['001', 3, 'annie', '2008-12-22 19:33', 'planned']:
   {'duration': 3600, 'start': 1229970800, 'created': ..., 'creator': 'annie'}>


Moving Work Items to Other Tasks
================================

  >>> wi10 = wi07.doAction('move', 'john')
  >>> wi10
  <WorkItem ['001', 4, 'john', '2008-12-22 19:33', 'delegated']:
   {'start': 1229970800, 'created': ..., 'source': '0000010', 'creator': 'john'}>


Queries
=======

Runs
----

  >>> list(tracks.runs)
  [2, 3, 4]
  >>> list(tracks.finishedRuns)
  [1]


Some Special Cases
==================

Close a run with some delegated items.

  >>> wi07a = workItems.query(run=2)[-2]
  >>> wi07b = workItems.query(run=2)[-1]
  >>> wi07b
  <WorkItem ['001', 2, 'john', '2008-12-22 19:33', 'moved']: {'start': 1229970800,
   'created': ..., 'target': '0000011', 'creator': 'john'}>

  >>> wi07c = wi07b.doAction('close', 'john')

  >>> wi07a.state
  'delegated_x'
  >>> wi07b.state
  'moved_x'

  >>> list(tracks.finishedRuns)
  [1, 2]
