Ross,
 
This comes at an opportune time, as I have been asked to do some workflow 
work.

Here's where you need authorization.  This scenario has two players, a 
department manager and the company accountant.  An invoice comes to the 
department manager, so at that point we might call its state received.  The 
department manager is the only one who knows whether the invoice is 
legitimate, so he is the only one who should be authorized to approve it, 
which would promote it to the next state.  At that point the accountant 
would pay the invoice and promote it to the finished state, which is 
posted.  Nobody else in the company should be able to do these things. 
 That's easily accomplished by having a relation between the steps table 
and auth_user.  Maybe groups need to be part of it, too.

Like this:
db.define_table('performers',
  Field('user_or_group', requires=IS_IN_SET(['auth_user', 'auth_group'])),
  Field('step_id', db.steps...)
  Field('entity_id', integer requires= # is in auth_user or auth_group.  I 
know there's a way to do this, but I can't think of it right now.

Also the point about concurrent tasks,  further down, is correct.  I think 
a table of dependencies, with links back to the steps table, would handle 
that.

I haven't debugged my data model completely, but this is my current state 
of thinking.

Cliff

On Wednesday, May 2, 2012 9:07:39 AM UTC-4, Ross Peoples wrote:
>
> Cliff,
>
> Thanks for the feedback. I added the ability to name / title steps. I also 
> added the ability to set priorities for workflows.
>
> It is assumed that when a step is complete, that step is already "done" 
> and the next step in workflow is "new". Each step has an order_id to show 
> which order the steps are in. The workflow table stores the "order_id" of 
> the active workflow step. So in this case if the workflow is on order_id 3, 
> then anything below 3 is considered done, while everything else is new. Is 
> this what you mean by states to account for?
>
> Active workflows can be modified on the fly as well. This has a limitation 
> that any step that has been completed cannot be modified. All future steps 
> can be modified. So if the workflow needs to have another step added to it 
> or if it has been determined that the next destination user_id / group_id 
> needs to be changed, that can be done (i.e. someone accidentally added the 
> Engineering group, but it should have been Quality instead).
>
> I will add another method to WorkflowEngine (I was going to do this 
> anyways, but forgot to include it in my original post:
> workflow = workflow_engine.get_workflow(workflow_id)
>
> # The contents of the workflow object would contain:
> #    name: the name of the workflow
> #    is_template: is this workflow a template
> #    item_type, item_id: what object is the workflow attached to
> #    order_id: the current position of the workflow
> #    priority: the priority of the workflow
> #    steps: list of step as Row objects
> #
> # There would be a few helper methods as well:
> #    current_step(): returns the Row object of the current step or None
> #    due_date(): returns the current step's due date or None
> #    ...
>
> I have not built in any security, as I figured that would be something 
> more application-specific. For example, a document management system might 
> want security on folders, just like a regular file system, and 
> not necessarily on a workflow-by-workflow basis.
>
> However, I am planning on adding auditing support. I almost used the new 
> record versioning feature, but decided I wanted to go with delta-based 
> changes instead of full copies of everything for better timeline support.
>
> Additional questions, comments, and improvements are welcome!
>
> On Tuesday, May 1, 2012 4:07:05 PM UTC-4, Cliff wrote:
>>
>> Ross,
>>
>> I like the on-the-fly ability.
>>
>> Each step in your template changes the state of the item.  Each one of 
>> those states should have a title.
>>
>> There are two other states to account for, new and done.
>>
>> Workflows sometimes have more than one path to completion.  
>>
>> I see an entity called a workflow_item that has the ability to determine 
>> its current state and all possible states to which it could transition.  It 
>> also knows who can cause it to transition to one of those states as well as 
>> which of its attributes should be hidden, visible or editable in any given 
>> state, and who should be able to see or edit those attributes while in that 
>> state.
>>
>>
>> On Tuesday, May 1, 2012 12:00:42 PM UTC-4, Ross Peoples wrote:
>>>
>>> In reference to: 
>>> https://groups.google.com/forum/#!searchin/web2py/workflow/web2py/osEmmtu9hlg/2MHi_ZCeMBMJ
>>>
>>> Has anyone done any work on this yet? I was thinking about making a 
>>> web2py-based workflow engine.
>>>
>>> I mentioned previously that I built one of these for an application I 
>>> wrote several years ago, but it was built specifically for that app. This 
>>> will be my first attempt at making a general-use workflow engine, so let me 
>>> know if you find any problems with my design:
>>>
>>> Make a contrib module that is initialized like the Auth module:
>>> from gluon.contrib.workflow_engine import WorkflowEngine, Step
>>> workflow_engine = WorkflowEngine()
>>> workflow_engine.define_tables()
>>>
>>>
>>> I will use the example that I know best, which is passing around a sales 
>>> order throughout a company's departments. This first example would define a 
>>> workflow template because every sales order will have the same workflow:
>>> workflow_engine.add_template('Sales Order',
>>>     Step(group_id=2, hours=4), # Engineering gets 4 hours to complete 
>>> their step
>>>     Step(user_id=7, hours=0), # The engineering manager (user) has no 
>>> time limit
>>>     Step(group_id=3, hours=2), # Quality department gets 2 hours to 
>>> inspect the order
>>>     Step(group_id=8, hours=6.5), # Shipping department gets 6.5 hours 
>>> to ship order
>>> )
>>>
>>>
>>> You would start this workflow like this:
>>> workflow_id = workflow_engine.load_template('Sales Order', item_type=
>>> 'document', item_id=1)
>>> workflow_id = workflow_engine.
>>> workflow_engine.start(workflow_id)
>>>
>>>
>>> Optionally, a workflow can be created on the fly (if the workflow will 
>>> only be used once):
>>> workflow_id = workflow_engine.create_workflow('One-time Workflow', 
>>> 'document', 1 # same as item_type and item_id used in load_template()
>>>     Step(group_id=2, hours=4)
>>>     Step(group_id=3, due_date=request.now + datetime.timedelta(days=1)) # 
>>> set time limit to an exact datetime
>>> )
>>>
>>> workflow_engine.start(workflow_id) # start the workflow we just created
>>>
>>>
>>> We assume that we are going to associate this workflow with another 
>>> object. In this case, we will assume there is a table called "document" and 
>>> that the document we want to pass around has an id of 1. The "item_type" 
>>> argument allows you to pass around any type of database object. In this 
>>> case, we will call it the same thing as our table: "document".
>>>
>>> These are some common operations that could be done:
>>> workflow_engine.active_workflows() # returns a list of all active 
>>> workflows
>>> workflow_engine.active_workflows(user_id=1) # all active workflows for 
>>> the user_id
>>> workflow_engine.active_workflows(user_id=1, include_groups=True) # same 
>>> as above, but includes groups the user is a member of
>>> workflow_engine.active_workflows(group_id=2) # all active workflows for 
>>> the group_id
>>> workflow_engine.late_workflows() # returns a list of all late/overdue 
>>> workflows
>>> workflow_engine.step_complete(workflow_id, notes='General info about 
>>> completed task') # moves workflow to the next step
>>> workflow_engine.step_reject(workflow_id, to_step=2, notes='Why step was 
>>> rejected') # moves workflow back to step 2 incase there was a problem 
>>> with one of the previously completed steps
>>>
>>>
>>> Workflow triggers:
>>> workflow_engine.before_start = function(workflow, step)
>>> workflow_engine.after_start = function(workflow, step)
>>> workflow_engine.before_step_complete = function(workflow, step)
>>> workflow_engine.after_step_complete = function(workflow, step)
>>> workflow_engine.before_step_reject = function(workflow, step)
>>> workflow_engine.after_step_reject = function(workflow, step)
>>> workflow_engine.before_finish = function(workflow, step)
>>> workflow_engine.after_finish = function(workflow, step)
>>>
>>>
>>> Finally, (and I MIGHT do this) since we are using time limits in hours, 
>>> we should set some time ranges where users are available. For example, if 
>>> the company is only open from 8 AM to 5 PM, you wouldn't want something to 
>>> be late at 7 PM. You would want to roll over the extra 2 hours so that it 
>>> becomes late at 10 AM the next business day. A list of time ranges would be 
>>> created, and a user would be assigned to one of the time ranges. This would 
>>> accommodate users in different time zones or with different "work" hours. 
>>> Again, this last part I MIGHT do if I have enough time. I've done it 
>>> before, but I'm sure you can imagine how complicated this part is.
>>>
>>>
>>> So any questions, comments, improvements? Thanks!
>>>
>>

Reply via email to