Application state machine

At its heart, approval workflows is a user-configurable state machine, and state is tracked via application records.

We have an application_state class, which represents a 'location' in a workflow. It doesn't say anything about how the application got to that state. Behaviours (such as which buttons are available) should be based on application state, and are configured through interactions.

We still have the ability to determine how an application got to a given state. We can check the 'last action' on an application. Some decisions will be based on this property, such as the ability to withdraw while the state is 'before submission'. The overall progress is calculated using the last action. But otherwise, behaviour should usually depend on the state alone.

The application state class is used to represent the following states:

  • There is a state for when the application has not yet been submitted for the first time, and is hidden to users who don't have one of the 'draft' capabilities. The state contains the first stage and CONDITION_DRAFT.
  • For every stage where there is a form view, there is a state containing the stage and CONDITION_BEFORE_SUBMISSION. This state even exists for the first stage - it is a distinct state from the initial draft state. An application that has been previously submitted cannot revert to draft.
  • There is a state to represent each approval level in a stage, with CONDITION_IN_APPROVALS.
  • Each stage which is a 'finished' stage has a state with CONDITION_FINISHED

All applications start in the draft state, with the current stage set to the first stage. The current_stage_id cannot be null.

The relationship between application states, actions, activities and events follows a consistent pattern:

For approve, reject and withdraw:

  • The application_action GraphQL endpoint triggers an application\action execute.
  • The action execute function records an application_action record and calls the appropriate application::<action> function.
  • The application::<action> function records application activities and changes the state of the application.
  • When an application activity is created it fires a corresponding event, where appropriate.

For submit:

  • The application_submit GraphQL endpoint saves the submission and marks it submitted, then triggers application::submit.
  • The application::submit function records application activities and changes the state of the application.
  • When an application activity is created it fires a corresponding event, where appropriate.