Ross, I understand your reasons for attaching approvals to the workflow. I do it the other way because if a deliverable needs approval by six parties, putting six additional steps in the flow makes things a little cluttered. If you add six people as performers of an approval step, does that mean that any one of the six can approve or must all of them?
I very much like your workflow object table. I'm still debating over whether to use auth to handle the roles. I think auth needs a table called auth_role, actually a group of groups, to collect permissions. It would be nice to have a 'has_role' decorator for it. On Wednesday, May 16, 2012 2:33:35 PM UTC-4, Ross Peoples wrote: > > 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). > >