16th September 2004
Amended 1st Nov 2016
As of 10th April 2006 the software discussed in this article can be downloaded from www.radicore.org
A computer application contains a number of different 'tasks', 'transactions', 'programs' or 'modules', each of which performs a particular function. Sometimes the processing of one particular task is supposed to be followed by one or more other tasks in order to complete some higher process. For example, the task 'Take Customer Order' may have to be followed by 'Charge Customer', 'Pack Order' and 'Ship Order'. This higher process may have a name such as 'Order Fulfillment', but as you can see it cannot be handled by a single task and has to be broken down into its component parts.
Without a workflow system the processing of the component parts has to be handled manually, which is where mistakes can occur. Forgetting to charge the customer or ship the order is not a good way to run a business.
With a Workflow system it is possible to define 'Order Fulfillment' as a workflow process, with 'Charge Customer', 'Pack Order' and 'Ship Order' as components of that process. When an instance (or 'case') of the workflow process is created the workflow engine will then take responsibility for dealing with each component in turn. These components may be executed automatically, or they may be directed to appear in someone's inbox.
What is a 'workflow' system? The Workflow Management Coalition defines 'workflow' as:
The automation of a business process, in whole or part, during which documents, information or tasks are passed from one participant to another for action, according to a set of procedural rules.
There can be two basic types of workflow:
This document will describe the activity based workflow system which I have constructed as an extension to my Development Infrastructure for PHP. This workflow system has the following component parts:
In order to implement a workflow system it is first necessary to find a suitable means of designing and modeling a workflow process. For this I take advantage of the work done by Carl Adam Petri who was the first to formulate a general theory for discrete parallel systems which gave birth to what are now known as Petri Nets.
Petri Nets is a formal and graphical language which is appropriate for modelling systems with concurrency and resource sharing. It is a generalisation of automata theory such that the concept of concurrently occurring events can be expressed.
Petri Nets have become so popular and widespread that there is a Platform Independent PetriNet Editor (PIPE) available, and it even has its own Petri Net Markup Language (PNML).
The Petri Net language contains the following basic objects:
Places | These are inactive and are analogous to inboxes in an office-based system. They are shown as circles in a Petri Net diagram. Each Petri Net has one start place and one end place, but any number of intermediate places. |
Transitions | These are active and represent tasks to be performed. They are shown as rectangles in a Petri Net diagram. |
Arcs | Each of these joins a single Place to a single Transition. They are shown as connecting lines in a Petri Net diagram. An inward arc goes from a Place to a Transition and an outward arc goes from a Transition to a Place. |
Tokens | These represent the current state of a workflow process. They are shown as black dots within Places in a Petri Net diagram. A place can hold zero or more tokens at any moment in time. |
These objects are subject to the following rules:
The time the transition is enabled and the time it fires are different. The thing that causes an enabled transition to fire is called a trigger. There are four different types of trigger:
Automatic | A task is triggered the moment it is enabled instead of being placed in a queue. | |
User | A task is triggered by a human participant, i.e., a user selects an enabled task instance to be executed. In a workflow management system each user has a so-called 'in-basket'. This in-basket, which will be displayed on the Menu/Home Page, contains task instances (workitems) that are enabled and may be executed by the user. By selecting and completing a workitem the corresponding task instance is triggered and the workflow case is advanced to the next stage in the process. | |
Time | An enabled task instance is triggered by a clock, i.e., the task is executed when a predefined deadline is passed. For example, the task 'remove document' is triggered if a case is trapped in a specific state for more than 15 hours.
This should be one of the options in an implicit OR split. Because this type of task can be triggered by a background process which runs at scheduled times it cannot have any dialog with the user. It is also possible to use an online screen to view timed events which have passed their deadline, from where individual workitems can be selected and triggered manually. |
|
Message | An external event (i.e. a message) triggers an enabled task instance. Examples of messages are telephone-calls, fax messages, e-mails or EDI messages.
Each of these external events will probably require some action within an application task so that the workflow system is made aware that the event has taken place. |
The route between the start place and the end place within a workflow process can take several forms. These are:
Sequential Routing |
Parallel Routing |
Conditional Routing |
Iterative Routing |
In order implement these routings you may employ a selection of splits and joins. These are:
AND split | |
An example of parallel routing where several tasks are performed in parallel or in no particular order. It is modeled by a transition with one input place and two or more output places. When fired the transition will create tokens in all output places. | |
AND join | |
A transition with two or more input places and one output place. This will only be enabled once there is a token in all of the input places, which would be after each parallel thread of execution has finished. | |
Explicit OR split | |
An example of conditional routing where the decision is made as early as possible. It is modeled by attaching conditions or guards to the arcs going out of a transition to different places.
Guard - An expression attached to an arc, shown in brackets, that evaluates to either TRUE or FALSE. Tokens can only travel over arcs when their guard evaluates to TRUE. The expression will typically involve the case attributes. More than two arcs of this type can come out of the same transition. They must all have a condition except the last arc as this will be used as the default path if none of the other conditions evaluates to TRUE. |
|
Implicit OR split | |
An example of conditional routing where the decision is made as late as possible. Implicit or-splits are modeled as two or more arcs going from the same place but to different transitions. That way, the transition that happens to fire first will get the token. Once the token is gone, the remaining transitions are cancelled and thus cannot be fired.
One of the transitions may have a timer as its trigger so that it will be fired if none of the other transitions is activated before the time limit expires. Expired transitions can either be triggered automatically via a background process which is running on a timer (e.g. cron), or manually via an online screen. |
|
OR join (explicit and implicit) | |
Is simply a place that serves as the output place of two different transitions. That way, the next transition after the or-join place will be enabled when either of the two conditional threads are done. |
A workflow is the formal definition of the process used to manage cases of a specific kind (e.g. order fulfillment, article publishing). Each kind of case will have its own workflow process. Here's an example of an order fulfillment process:
Order Fulfillment workflow
The explanation of this diagram is as follows:
Places are inactive. All the places do is hold tokens representing the state of the process. If, for example, there's a token in place D above, then that means we're ready to pack the order.
Transitions are active. They move tokens from their input places (the places that have an arc pointing into the transition) to their output places (the places you get to by following the arcs going out of the transition). When this happens the transition is said to fire.
Transitions can only fire when there's at least one FREE token in each input place. When that is the case, the transition is enabled. That the transition is enabled means it is able to fire. It will fire when the conditions of its trigger are satisfied.
When the workflow is started, a token is placed in the start place (A in the example). This enables the automatic transition 'Charge Credit Card'.
The transition fires with a success or a failure. If it was successful, it produces a token in place D. If there was a failure, it produces a token in place B. Thus, the outcome of the attempt at charging the credit card governs the further routing of the process. The rule is that firing a transition consumes one token from each of its input places, and places a token on each of its output places for which the guard is true. The guard is a predicate, in this case the [success] and [failure] on the arcs going out of 'Charge Credit Card'. Guards are what enables us to do conditional routing. The 'Charge Credit Card' transition acts as an explicit or-split, because it chooses either one route or the other.
The other form of conditional routing, which is the implicit or-split, is what chooses between the transitions 'Update Billing Information' and 'Cancel Order'. Since there's only one token in place C, only one of the two transitions can have it. But, contrary to the explicit or-split, where the decision is explicitly made as soon as 'Charge Credit Card' finishes, the choice between 'Update Billing Information' and 'Cancel Order' is made as late as possible.
Both transitions will be enabled when there's a token in place C (i.e. when the spam has been sent). If the user updates his billing information before the timed 'Cancel Order' transition times out, 'Cancel Order' is never fired. And vice versa: If the order is canceled (which will probably involve spamming the user again to let him know that his order was cancelled), then he won't be able to update his billing information and will have to enter a new order. Thus, the choice is made implicitly, based on the timing.
The guard will generally depend on case attributes. The 'Charge Credit Card' transition above will set a case attribute to either 'success' or 'failure', and the guard will check this value to determine its result. Case attributes can hold more complex values than simple yes/no values, but the guard must always be either true or false.
What is missing from this diagram is the process which initiates a new workflow instance (or 'case') and puts a token in the start place. In the above example the case initiator would be 'Take Customer Order'. In this implementation the activity which initiates a workflow case is shown as start_task_id on the WORKFLOW table.
The main issue for a workflow management system is answering the question 'who must do what, when and how
'. Some of these components already exist within my application, which is available for download from http://www.radicore.org, while others will need to be created separately.
What | These are the transitions which represent tasks, or something to be done such as: giving authorization, updating a database, sending an e-mail, loading a truck, filling a form, printing a document and so on. These are the identities of application tasks and will have an entry on the TASK table within the MENU database. |
When | When a transition or task is performed depends on its position within the workflow process and the placing of tokens on all its input places during the execution of each case. |
How | Each transition or task will point to a particular entry on the TASK table within the MENU database which in turn will provide the location and name of the application script which will carry out the necessary processing. |
Who | A transition or task which must be triggered by a human participant may be assigned to a single human or a group of humans. Within my MENU database individual people are identified on the USER table and groups of people are identified on the ROLE table. |
Here is the Entity-Relationship (E-R) Diagram for my workflow tables:
E-R Diagram
These tables are for defining workflow processes. | |
WORKFLOW | This table holds the definition for each workflow process, such as 'Order Fulfillment'. |
PLACE | This table holds the details for each place within a workflow process. |
TRANSITION | This table holds the details for each transition within a workflow process, such as 'Charge Customer', 'Pack Order' and 'Ship Order'. Each record will point to an application task within the MENU database. |
ARC | This table holds the details for each arc within a workflow process. An arc links a place to a transition. |
These tables are for individual workflow instances or cases. | |
CASE | This identifies when a particular instance of a workflow was started, its context and its current status. It is possible for a workflow to have multiple open cases at any one time. |
TOKEN | This identifies when a token was inserted into a particular place. |
WORKITEM | A record is created here when a transition is enabled or able to fire. Entries which are to be triggered by a human participant will appear on the Menu/Home Page of relevant users so that they can see what tasks are pending and select any for processing. Each of these entries will be a hyperlink which, when pressed, will cause the relevant application task to be activated with the correct context already loaded. |
The structure of this table is as follows:
CREATE TABLE `wf_workflow` ( `workflow_id` smallint(5) unsigned NOT NULL default '0', `workflow_name` varchar(80) NOT NULL default '', `workflow_desc` text, `start_task_id` varchar(40) NOT NULL default '', `is_valid` char(1) NOT NULL default 'N', `workflow_errors` text, `start_date` date default NULL, `end_date` date default NULL, `created_date` datetime NOT NULL default '0000-00-00 00:00:00', `created_user` varchar(16) default NULL, `revised_date` datetime default NULL, `revised_user` varchar(16) default NULL, PRIMARY KEY (`workflow_id`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
workflow_id | NUMERIC | Unique identity assigned by the system. |
workflow_name | STRING | Required. Short name. |
workflow_desc | STRING | Optional. Long description. |
start_task_id | STRING | Required. The identity of the application task which, when executed, creates a new workflow case and puts a token on the start place. |
is_valid | BOOLEAN | Default is NO. After defining all the places, transitions and arcs for a workflow process it must be validated before it can be used. This field shows the result of that validation. |
workflow_errors | STRING | Read only. This contains any error messages from the last validation process. If there are errors then IS_VALID is set to NO. |
start_date | DATE | Required. Identifies the date on which this workflow process, if valid, comes into existence. It cannot be used to create cases before this date. |
end_date | DATE | Optional. Identifies the date on which this workflow process ceases to be valid. It cannot be used to create cases after this date.
The start and end dates can thus be used to phase one definition out and bring another one in on a particular date. |
The following fields are set automatically by the system: | ||
created_date | DATE+TIME | The date and time on which this record was created. |
created_user | STRING | The identity of the user who created this record. |
revised_date | DATE+TIME | The date and time on which this record was last changed. |
revised_user | STRING | The identity of the user who last changed this record. |
The structure of this table is as follows:
CREATE TABLE `wf_place` ( `workflow_id` smallint(5) unsigned NOT NULL default '0', `place_id` smallint(5) unsigned NOT NULL default '0', `place_type` char(1) NOT NULL default '5', `place_name` varchar(80) NOT NULL default '', `place_desc` text, `created_date` datetime NOT NULL default '0000-00-00 00:00:00', `created_user` varchar(16) default NULL, `revised_date` datetime default NULL, `revised_user` varchar(16) default NULL, PRIMARY KEY (`workflow_id`,`place_id`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
workflow_id | NUMERIC | Required. Points to an entry on the WORKFLOW table. |
place_id | NUMERIC | Unique identity assigned by the system. |
place_type | STRING | Required. Valid options are:
|
place_name | STRING | Required. Short name. |
place_desc | STRING | Optional. Long description. |
The structure of this table is as follows:
CREATE TABLE `wf_transition` ( `workflow_id` smallint(5) unsigned NOT NULL default '0', `transition_id` smallint(5) unsigned NOT NULL default '0', `transition_name` varchar(80) NOT NULL default '', `transition_desc` text, `transition_trigger` varchar(4) NOT NULL default 'USER', `time_limit` int(11) unsigned default NULL, `task_id` varchar(40) NOT NULL default '', `role_id` varchar(16) default NULL, `created_date` datetime NOT NULL default '0000-00-00 00:00:00', `created_user` varchar(16) default NULL, `revised_date` datetime default NULL, `revised_user` varchar(16) default NULL, PRIMARY KEY (`workflow_id`,`transition_id`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
workflow_id | NUMERIC | Required. Points to an entry on the WORKFLOW table. |
transition_id | NUMERIC | Unique identity assigned by the system. |
transition_name | STRING | Required. Short name. |
transition_desc | STRING | Optional. Long description. |
transition_trigger | STRING | Required. Identifies how this transition will be triggered. Valid options are:
A transition cannot be fired until there is at least one FREE token on each of its input places. If it is fired successfully then tokens will be created on each of its output places. |
time_limit | NUMERIC | Optional. Only valid if transition_trigger='TIME'. It is a value in minutes, but is displayed and input in hours and minutes. Valid values are between '0:01' and '999:59'. |
task_id | STRING | Required. The identity of the application task which will be activated when this transition is fired.
Note that a task which has been identified as the starting task for a workflow cannot also be used as a transition within any workflow as the act of firing that transition within a workflow case will cause the creation of a new workflow case. |
role_id | STRING | Optional. The identity of the group of users (ROLE) to which this workitem will be assigned when the entry is created. If this is non-blank the corresponding workitem will be available to all users who share that role and not a single specified user. |
The structure of this table is as follows:
CREATE TABLE `wf_arc` ( `workflow_id` smallint(5) unsigned NOT NULL default '0', `transition_id` smallint(5) unsigned NOT NULL default '0', `place_id` smallint(5) unsigned NOT NULL default '0', `direction` char(3) NOT NULL default '', `arc_type` varchar(10) NOT NULL default 'SEQ', `condition_field` varchar(40) NULL, `condition_operator` varchar(4) NULL, `condition_value` varchar(40) NULL, `created_date` datetime NOT NULL default '0000-00-00 00:00:00', `created_user` varchar(16) default NULL, `revised_date` datetime default NULL, `revised_user` varchar(16) default NULL, PRIMARY KEY (`workflow_id`,`transition_id`,`place_id`,`direction`), KEY `place_id` (`workflow_id`,`place_id`,`direction`), KEY `transition_id` (`workflow_id`,`transition_id`,`direction`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
workflow_id | NUMERIC | Required. Points to an entry on the WORKFLOW table. |
transition_id | NUMERIC | Required. Points to an entry on the TRANSITION table. |
place_id | NUMERIC | Required. Points to an entry on the PLACE table. |
direction | STRING | Required. The possible options are:
|
arc_type | STRING | Required. Identifies if the arc is a join or split within the current routing. Possible options are:
|
condition_field | STRING | Optional. Only valid if 'Arc Type' is set to Explicit OR split.
If this field is supplied then condition_operator and condition_value must also be supplied. They will be used in the sequence " Must be in the format 'name' or 'strlen(name)' where 'name' identifies a field in the current A transition can have two or more output arcs of this type, and a condition must be defined in all except the last one. At runtime these entries will be sorted by place_name and the object's current data, as held in Input values such as ' if ($fieldarray['name1'] > 10) { return TRUE; } else { return FALSE; } // if Input values such as ' if (strlen($fieldarray['name1']) > 10) { return TRUE; } else { return FALSE; } // if Input values such as ' if ($fieldarray['name1'] > $fieldarray['name2']) { return TRUE; } else { return FALSE; } // if |
condition_operator | STRING | Optional. If condition_field is not empty then this must also be not empty. Valid values are:
|
condition_value | STRING | Optional. If condition_field is not empty then this must also be not empty.
This can be a number or a string. If it is a string which matches the name of an entry in the current |
The structure of this table is as follows:
CREATE TABLE `wf_case` ( `case_id` int(10) unsigned NOT NULL default '0', `workflow_id` smallint(5) unsigned NOT NULL default '0', `context` varchar(255) NOT NULL default '', `case_status` char(2) NOT NULL default 'OP', `start_date` datetime NOT NULL default '0000-00-00 00:00:00', `end_date` datetime default NULL, `created_date` datetime NOT NULL default '0000-00-00 00:00:00', `created_user` varchar(16) default NULL, `revised_date` datetime default NULL, `revised_user` varchar(16) default NULL, PRIMARY KEY (`case_id`), KEY `workflow_id` (`workflow_id`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
case_id | NUMERIC | Unique identity assigned by the system. |
workflow_id | NUMERIC | Required. Points to an entry on the WORKFLOW table. |
context | STRING | The primary key of the database entry to which this case refers in the format of an sql WHERE clause, as explained in Appendix I. This is produced by the application task identified in START_TASK_ID on the WORKFLOW table. |
case_status | STRING | Required. Possible options are:
|
start_date | DATE+TIME | Set by the system when this entry is opened. |
end_date | DATE+TIME | Set by the system when this entry is closed. This occurs when a token is placed in the end place. |
The structure of this table is as follows:
CREATE TABLE `wf_token` ( `case_id` int(10) unsigned NOT NULL default '0', `token_id` smallint(5) unsigned NOT NULL default '0', `workflow_id` smallint(6) unsigned NOT NULL default '0', `place_id` smallint(5) unsigned NOT NULL default '0', `context` varchar(255) NOT NULL default '', `token_status` varchar(4) NOT NULL default 'FREE', `enabled_date` datetime NOT NULL default '0000-00-00 00:00:00', `cancelled_date` datetime default NULL, `consumed_date` datetime default NULL, PRIMARY KEY (`case_id`,`token_id`), KEY `place_id` (`workflow_id`,`place_id`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
case_id | NUMERIC | Required. Points to an entry on the CASE table. |
token_id | NUMERIC | Unique identity assigned by the system. |
workflow_id | NUMERIC | Required. Points to an entry on the WORKFLOW table. |
place_id | NUMERIC | Required. Points to an entry on the PLACE table. |
context | STRING | The primary key of the database entry as passed down by the previous transition (application task). |
token_status | STRING | Required. Possible options are:
When a token is created it will automatically have a FREE status. A token on an input place must be FREE before a transition can be fired. |
enabled_date | DATE+TIME | The date and time on which this token appeared in this place. |
cancelled_date | DATE+TIME | The date and time on which this token was cancelled. |
consumed_date | DATE+TIME | The date and time on which this token was consumed by a transition being fired. |
The structure of this table is as follows:
CREATE TABLE `wf_workitem` ( `case_id` int(10) unsigned NOT NULL default '0', `workitem_id` smallint(5) unsigned NOT NULL default '0', `workflow_id` smallint(6) unsigned NOT NULL default '0', `transition_id` smallint(5) unsigned NOT NULL default '0', `transition_trigger` varchar(4) NOT NULL default 'USER', `task_id` varchar(40) NOT NULL default '', `context` varchar(255) NOT NULL default '', `workitem_status` char(2) NOT NULL default 'EN', `enabled_date` datetime default NULL, `cancelled_date` datetime default NULL, `finished_date` datetime default NULL, `deadline` datetime default NULL, `role_id` varchar(16) default NULL, `user_id` varchar(16) default NULL, PRIMARY KEY (`case_id`,`workitem_id`), KEY `transition_id` (`workflow_id`,`transition_id`) ) ENGINE=MyISAM;
Field | Type | Description |
---|---|---|
case_id | NUMERIC | Required. Points to an entry on the CASE table. |
workitem_id | NUMERIC | Unique identity assigned by the system. |
workflow_id | NUMERIC | Required. Points to an entry on the WORKFLOW table. |
transition_id | NUMERIC | Required. Points to an entry on the TRANSITION table. |
transition_trigger | STRING | Set by the system. Shows how this transition was fired. Valid options are:
A transition cannot be fired until there is at least one FREE token on each of its input places. If it is fired successfully then tokens will be created on each of its output places. |
task_id | STRING | Identity of an entry on the TASK table in the MENU database that will be activated when this workitem is processed. |
context | STRING | The primary key of a database entry that will be passed to the application task when this workitem is processed. |
workitem_status | STRING | Required. Possible options are:
|
enabled_date | DATE+TIME | The data and time on which this workitem was enabled. |
cancelled_date | DATE+TIME | The data and time on which this workitem was cancelled. |
finished_date | DATE+TIME | The data and time on which this workitem was finished. |
deadline | DATE+TIME | If the transition_trigger='TIME' this is the date and time on which the deadline expires. |
role_id | STRING | Identity of an entry on the ROLE table in the MENU database. |
user_id | STRING | Identity of an entry on the USER table in the MENU database. |
These are the maintenance screens for the Workflow system:
Having defined a workflow process the next step is to have a means whereby instances (or 'cases') of that process can be created then progressed from one stage to the next. This is where the 'engine' comes in. This is the piece of software that monitors every action to see if it requires the creation of a new workflow case or the updating of an existing one.
One feature of my infrastructure design is that every database access (select, insert, update and delete) goes through my abstract table class. Because of this design feature I therefore have a single class in which it is possible to trap all database activity within the system, and therefore I can implement my workflow engine without having to make modifications to huge numbers of scripts.
There are two significant points to consider with this implementation:
No application task is aware that is part of a workflow, and none of the code within an application task has to be amended in order for it to be included in a workflow case.
The workflow system itself does not contain any application code - all it does is sit in the background 'sniffing' at each task when it runs to find out if it is part of a workflow case or not. If it is, then when that task (workitem) is terminated (fired) the workflow system will move tokens from one place to another, which may cause another workitem (task) to be enabled.
Trapping when a new database record has just been created is very simple as new records are created through the insertRecord() method of my generic table class. Similarly all updates go through the updateRecord() method. All I have to do is insert the following code at the end of these methods:
if (empty($this->errors)) { // additions to the workflow database do not count if ($this->dbname != 'workflow') { // find out if this task/context has any workflow connections $this->_examineWorkflow($fieldarray); } // if } // if return $fieldarray; } // insertRecord
The _examineWorkflow() method will perform the following:
This involves looking for an outstanding WORKITEM during the execution of the insertRecord() and updateRecord() methods which matches the current task identity and context. The selection criteria is as follows:
Thus 'Pack Order where customer_id=1234' will involve a different WORKITEM to 'Pack Order where customer_id=5678'.
If a record is found where its primary key matches the current context then the following checks are made:
$this->wf_user_id
.If the insert or update is successful then the transition/workitem is said to have been fired, which has the following effect:
Note that a transaction which is included in a workflow must perform a database commit() (with or without an actual database update) so that the workflow engine can be informed that the transaction has been completed successfully. If the transaction terminates without performing a commit() then the workflow engine will take no action.
Whenever a token is created in a place the following checks are made:
Tasks in the menu system are designed to be run in one of two ways:
Batch tasks are run in the background, do not have any dialog with the user, can be run automatically at scheduled times via cron or a task scheduler, and can stay active for extended periods.
When a transition is processed and a token is moved from an input place to an output place, this may enable a subsequent transition. If this subsequent transition has an AUTO trigger then it will be run immediately. While it is possible for an online transition to be followed by an AUTO-triggered transition which is either online or batch, it is only possible for a batch transition to be followed by an AUTO-triggered transition which is also batch. It is not possible for a batch transition to be followed by an AUTO-triggered transition which is designed to be run online.
As you can see designing and building a workflow system is not a trivial matter. The major steps are:
I do not claim that this design is perfect, but how does it compare to yours?
01 Nov 2016 | Added References
Updated Frequently Asked Questions (FAQ) Amended implicit OR split to allow more than two arcs, and removed the restriction that one of them must be triggered by a timer. |
30 Jan 2016 | Added Running batch tasks with AUTO triggers. |
01 May 2015 | Amended ARC table to split the pre_condition column into separate parts called condition_field, condition_operator and condition_value. |
01 May 2010 | Amended TRANSITION table to allow the time_limit to be specified in hours and minutes instead of just hours. |
01 Mar 2010 | Modified Explicit OR split to allow more than two arcs of this type to come out of a transition.
Updated Frequently Asked Questions (FAQ). |
18 Oct 2009 | Added Frequently Asked Questions (FAQ). |
20 Aug 2007 | Added the ability to fire the triggers of workitems with deadlines which have passed via an online screen instead of a background process. |