Dynamic transitions with the Workflow module

Workflows are a basic building block of business applications. While the Workflow module provides a good base to start building workflows in Drupal, it has not kept up with the rise in complexity of Drupal application requirements.

Central to a workflow is the notion of state transition: when does a "Job Bid" node move from state "Proposed" to state "Accepted" or "Rejected"? Who can trigger this transition? There are obviously n2 possible transitions for an n-state workflow, making what is commonly called a state transition matrix.

In the Workflow module, this matrix is data-driven: it is stored in table {workflow_transitions} that specifies each allowed transition (from state x to state y) and the user roles that are allowed to perform the transition. While this representation is fine for simple publishing workflows, it quickly becomes inflexible for more complex scenarios, that usually lead to abandoning the module altogether in favour of a hand-made (and therefore coding-intensive) solution.

Here's a scenario I'm working on: site members can bid on a Job by creating a Job Bid. Initially, the bid is stored as Draft, then it moves to the Negotiation state where both buyer and supplier can exchange comments, then the buyer (not the node author who is the supplier) can accept or reject the bid, moving it to states Accepted or Rejected, respectively. Today, this last transition is impossible to specify in Workflow.

To overcome this problem, I submitted a patch to Workflow that enables 3rd-party modules to specify dynamic transitions, that is, transitions that are not stored in the database, but rather computed on the fly. Through a couple of new hooks that Workflow invokes, new transitions can be added to the workflow at run-time and these additions can also be documented. For the application developer, it is now possible to write a business-logic module (that is usually application-specific) and that implements these two hooks:

<?php
// @file mymodule.module

// Define the sids we care about.
define('WF_STATE_NEGOTIATING', 1);
define('WF_STATE_ACCEPTED', 2);
define('WF_STATE_REJECTED', 3);

/**
 * Implementation of hook_workflow_transitions_info().
 */
function mymodule_workflow_transitions_info() {
  return array(
   
WF_STATE_NEGOTIATING => array(
     
WF_STATE_ACCEPTED => t('Let the job buyer accept the bid'),
     
WF_STATE_REJECTED => t('Let the job buyer reject the bid'),
    ),
  );
}

/**
 * Implementation of hook_workflow_transitions_alter().
 */
function mymodule_workflow_transitions_alter(&$transitions, $sid, $account, $node) {
  if (
$sid == WF_STATE_NEGOTIATING) { // Let the job buyer accept or reject
   
$job = node_load($node->field_job[0]['nid']);
    if (
$job->uid == $account->uid) {
     
$transitions[WF_STATE_ACCEPTED] = workflow_get_state_name(WF_STATE_ACCEPTED);
     
$transitions[WF_STATE_REJECTED] = workflow_get_state_name(WF_STATE_REJECTED);
    }
  }
}

?>

At the time of writing, this patch hasn't yet been addressed by the module maintainers, so feel free to discuss it, preferably in the original issue thread.