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