Skip to content

Running a workflow as a case

Some work has no fixed order. A claim handler may ask for an expert review, call the customer, or decide, in whatever order the claim needs: some of these several times, some never. Drawing every possible order as flows would be unreadable. What matters is what may be done at any moment, and when the claim is closed.

Such a workflow is a case: its main flow only keeps it open, and the work is offered by handlers, each while its condition holds.

An example: a claim

flowchart LR
  s([Start]) --> r[Register the claim] --> o[Case open] --> e([End])
  x[["Request an expert review<br/>while amount > 10000"]]
  c[["Call the customer"]]
  d[["Decide<br/>while amount <= 10000, or once reviewed"]]
  done[["Everything is done<br/>once Register the claim and Decide went on"]] --> e2([Closed])
  • The main flow registers the claim, then waits at Case open, a Wait step nothing signals. It only keeps the instance open.
  • Request an expert review is a handler offered only while the amount is over 10,000, as often as needed.
  • Call the customer is a handler offered from the start, as often as needed.
  • Decide is a handler offered while the amount is at most 10,000, or once the expert review has been requested: a large claim is not decided without one. It writes its outcome to decision.
  • Everything is done is a pass-through handler. It goes on as soon as Register the claim and Decide have both gone on, in whatever order, interrupts the rest, and ends the case. The two are the case's required items: the expert review and the call are optional, so the condition does not name them.
nodes:
  n_start: { id: n_start, label: Start, type: start }
  n_register:
    id: n_register
    label: Register the claim
    type: user_operation
    config: { outcomes: registered }
    features:
      assignments: [{ plugin: roles, settings: { roles: [claims_handler] } }]
  n_open: { id: n_open, label: Case open, type: wait }
  n_end: { id: n_end, label: End, type: end }
  n_expert:
    id: n_expert
    label: Request an expert review
    type: user_operation
    features:
      assignments: [{ plugin: roles, settings: { roles: [claims_handler] } }]
      handler:
        mode: non_interrupting
        repeat: true
        condition: { plugin: comparison, settings: { variable: amount, operator: '>', value: 10000 } }
  n_call:
    id: n_call
    label: Call the customer
    type: user_operation
    features:
      assignments: [{ plugin: roles, settings: { roles: [claims_handler] } }]
      handler: { mode: non_interrupting, repeat: true }
  n_decide:
    id: n_decide
    label: Decide
    type: user_operation
    config: { outcomes: 'accepted,refused', payload_variable: decision }
    features:
      assignments: [{ plugin: roles, settings: { roles: [claims_handler] } }]
      handler:
        mode: non_interrupting
        condition:
          plugin: any
          settings:
            conditions:
              - { plugin: comparison, settings: { variable: amount, operator: '<=', value: 10000 } }
              - { plugin: step_completed, settings: { step: n_expert } }
  n_done:
    id: n_done
    label: Everything is done
    type: passthrough
    features:
      handler:
        mode: interrupting
        condition:
          plugin: all
          settings:
            conditions:
              - { plugin: step_completed, settings: { step: n_register } }
              - { plugin: step_completed, settings: { step: n_decide } }
  n_closed: { id: n_closed, label: Closed, type: end }
  n_end_expert: { id: n_end_expert, label: Review requested, type: end }
  n_end_call: { id: n_end_call, label: Customer called, type: end }
  n_end_decide: { id: n_end_decide, label: Decided, type: end }
flows:
  - { id: f1, from: n_start, to: n_register }
  - { id: f2, from: n_register, to: n_open }
  - { id: f3, from: n_open, to: n_end }
  - { id: f4, from: n_expert, to: n_end_expert }
  - { id: f5, from: n_call, to: n_end_call }
  - { id: f6, from: n_decide, to: n_end_decide }
  - { id: f7, from: n_done, to: n_closed }

The orchestra_examples submodule ships this workflow as example_claim_case, its tasks offered to the examples' reviewer role.

What happens:

  1. The claim starts with an amount of 6,000. The claim handler sees Register the claim, Call the customer and Decide. Request an expert review is held: the trace shows it as Waiting for its condition.
  2. The amount becomes 25,000: a step writes it as it goes on, a module writes it from outside a step (setVariable()), or an administrator changes it on the claim's page (see Changing a variable). Either way the conditions are tested again: Request an expert review is offered, and Decide is withdrawn, since a claim this large needs a review first.
  3. The claim handler requests a review: Decide is offered again, and so is the review, which can be requested as often as needed. The claim handler calls the customer: that step is offered again too.
  4. The claim handler decides: refused, written to decision. Decide has gone on, but Register the claim has not, so the case stays open. Decide is not offered again: it goes on once.
  5. The claim handler registers the claim. Both required items have gone on now, so Everything is done goes on. It cancels Case open and every item still offered, and the instance completes at Closed.

An item done a number of times

A building permit needs at least two site visits before it is decided. A visit can be recorded as often as needed, and Decide is offered only once two have been:

n_visit:
  id: n_visit
  label: Record a site visit
  type: user_operation
  config: { outcomes: recorded }
  features:
    assignments: [{ plugin: roles, settings: { roles: [inspector] } }]
    handler: { mode: non_interrupting, repeat: true }
n_decide:
  id: n_decide
  label: Decide
  type: user_operation
  config: { outcomes: 'granted,refused', payload_variable: decision }
  features:
    assignments: [{ plugin: roles, settings: { roles: [inspector] } }]
    handler:
      mode: non_interrupting
      condition: { plugin: step_completed, settings: { step: n_visit, times: 2 } }

Decide is held until Record a site visit has gone on twice: the second visit going on tests it, and it is offered. A condition on what the steps did, rather than on a variable they write, needs no step to remember to set a flag.

How it works

  • Items are handlers. A handler waits from the start, beside the rest. Non-interrupting, it leaves the rest running. Set to go on more than once, it is offered again each time it has been done. See Handlers.
  • An item is offered only while its condition holds. The condition is tested again each time something it reads changes: a variable it reads, or a step it reads going on. When it stops holding, the item is withdrawn. See Only while a condition holds.
  • The case closes itself, or is closed. A handler on a step that does not wait goes on as soon as its condition holds: interrupting, it cancels the rest and ends the case. Its condition can read which steps have gone on, as here: step_completed holds once a step has gone on, so an all of them closes the case once every required item is done. It can also read what the steps wrote, a variable. A person can close it too, with an interrupting handler that is a task.
  • The rest ending ends the items. When the main flow ends, the items still offered are canceled, the rest has ended.

Things to know

  • The main step must not time out. Case open is a Wait with no timeout. If the site sets a global default timeout, give the step a timeout of its own long enough for any case.
  • A condition reads the instance's variables. The variables a branch keeps for itself are not there. Have the steps write what the conditions read for the whole instance, which is what they do unless their completion scope is set to the token.
  • Every item waits on the same instance. Each is a step of the instance, so the inbox, the pending actions, the My requests card and the trace show them as they show any step.