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:
- 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.
- 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. - 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.
- 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. - 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_completedholds once a step has gone on, so anallof 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.