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