The data model I already have does things a bit differently, but I think it 
accomplishes the same thing. I am in the process of writing all of the 
support methods.

This is my current data model:

# workflow table
db.define_table('workflow',
    Field('name', length=50),
    Field('is_template', 'boolean', default=False),
    Field('created_by', db.auth_user, default=current_user),
    Field('created_on', 'datetime', default=self.request.now),
    Field('table_name', length=128),
    Field('row_id', 'integer'),
    Field('order_id', 'integer', default=self.ORDER_ID_STOP, 
comment='Current position of the workflow'),
    Field('priority', 'integer', requires=IS_INT_IN_RANGE(1, 9), 
default=self.DEFAULT_PRIORITY)
)

# allow users / groups to "monitor" select workflows without needing to be 
a part of the workflow
# if workflow is template, this list is copied to the new workflow
db.define_table('workflow_monitor',
    Field('workflow_id', db.workflow),
    Field('user_id', db.auth_user),
    Field('group_id', db.auth_group),
    Field('viewed', 'boolean', default=False) # once the monitor looks at 
it, mark as viewed until another change happens
)

# comments can be attached to workflows so that users can voice questions 
and concerns
db.define_table('workflow_comment',
    Field('workflow_id', db.workflow),
    Field('user_id', db.auth_user, default=current_user),
    Field('event_date', 'datetime', default=self.request.now),
    Field('is_active', 'boolean', default=True, comment='Is the comment 
waiting to be addressed'),
    Field('comment', length=512, comment='The question, comment, or 
concern'),
    Field('response', length=512, comment='Response to the question, 
comment, or concern')
)

# high-level list of goals for workflows. Users mark items as completed as 
they complete the goals
# if workflow is template, the checklist is copied to new workflow
db.define_table('workflow_checklist',
    Field('workflow_id', db.workflow),
    Field('name', length=50),
    Field('order_id', 'integer', comment='Ordering position of the item'),
    Field('is_active', 'boolean', default=True, comment='Is the item 
waiting to be addressed'),
    Field('completed_by', db.auth_user),
    Field('completed_at', 'datetime'),
    Field('completed_note', length=512)
)

# workflow step table
db.define_table('workflow_step',
    Field('workflow_id', db.workflow),
    Field('order_id', 'integer', comment='Ordering position of the step'),
    Field('name', length=50),
    Field('user_id', db.auth_user),
    Field('group_id', db.auth_group),
    Field('hours', 'decimal(10,2)', requires=IS_DECIMAL_IN_RANGE(0), 
default=0, comment='Optional time limit in hours'),
    Field('due_date', 'datetime', comment='Optional due date'),
    Field('note', length=512, comment='Note on current state of the step') 
# represents a stateful note (i.e. kickback reason). Can change at any time.
)

# audit tables
db.define_table('workflow_event',
    Field('workflow_id', 'integer'),
    Field('table_name', length=128), # i.e. document, folder, etc
    Field('row_id', 'integer'), # i.e. the ID of the document
    Field('user_id', db.auth_user, default=current_user),
    Field('event_date', 'datetime', default=self.request.now),
    Field('action', length=10, default='update'), # could be: create, 
update, delete
    Field('field_name', length=128), # i.e order_id, name, hours, etc
    Field('value_before', length=128), # None if create or delete
    Field('value_after', length=128) # None if delete
)

db.define_table('workflow_step_event',
    Field('workflow_id', 'integer'),
    Field('step_id', 'integer'), # the ID of the workflow_step modified 
(used to match up fields in batch changes)
    Field('user_id', db.auth_user, default=current_user),
    Field('event_date', 'datetime', default=self.request.now),
    Field('action', length=10, default='update'), # could be: create, 
update, delete
    Field('field_name', length=128), # i.e order_id, name, hours, etc
    Field('value_before', length=128), # None if create or delete
    Field('value_after', length=128) # None if delete
)


As you can see, this model has quite a few of the features we discussed: 
workflows, templates, monitors, comments, checklists, and auditing. It also 
has triggers that applications can register (i.e. before_step_complete, 
after_step_complete, etc) so applications can interact with the workflow 
engine when an event is triggered.

Workflows are copied from templates so that changes can be made to 
templates and workflows without one affecting the other. This has worked 
very well in my previous implementation. Tying monitors to templates 
wouldn't work well like this. Attaching monitors to workflows themselves 
also offers the advantage of giving the monitors a read/unread indicator 
and when a workflow has been changed (i.e step completed), the indicator 
turns back to "unread" so that way they keep up with workflows and not have 
to remember if a workflow has changed since the last time they looked at it.

My thinking on approvers is that they need to be part of the workflow. If 
the workflow cannot progress until it has been approved, then it should be 
a step in the workflow. If the approver finds something wrong, they can 
reject their step (with notes as to why) and send it back to anyone in the 
chain. Making approvals part of the workflow also has the advantage of 
optionally setting a time limit for the approver to make a decision.

I haven't made any attempts to add authorization functionality yet. I'm 
thinking about using the web2py authorization system for this. You would 
set up groups, add users to groups, and add workflow permissions to the 
groups. For example:

# allow group_id to create workflows on-the-fly
auth.add_permission(group_id, 'create', 'workflow', 0)

# allow group_id to load templates
auth.add_permission(group_id, 'load_template', 'workflow', 0)

# allow group_id to create templates
auth.add_permission(group_id, 'create_template', 'workflow', 0)

# give comment permission to group_id for workflow_id
auth.add_permission(group_id, 'comment', 'workflow', workflow_id)

# allow group_id to step-complete or reject steps for workflow_id
auth.add_permission(group_id, 'flow', 'workflow', workflow_id)

# allow group_id to modify workflow steps for workflow_id
auth.add_permission(group_id, 'update', 'workflow', workflow_id)

I would probably have the engine create a default set of groups and 
permissions on first-run. For example, maybe groups called "Workflow 
Monitor", "Workflow Participant", 'Workflow Creator", "Workflow Template 
Creator". Each workflow would have permissions set for it that can be 
predefined in templates (which are then copied to workflows when created).

Reply via email to