Skip to content

Tenant configuration

The developer defines a workflow; each tenant decides whether it uses it and who decides there. Changes of the configuration need a second administrator.

  1. Operators make a workflow available to a tenant:

    Terminal window
    php spark anvil:workflow:enable order-approval --tenant acme
  2. Administrators of the tenant (permission workflows.configure) open Access → Workflows and activate it. While a workflow is inactive, records keep their state, but no transitions run, nothing is locked and no tasks are created.

  3. anvil:workflow:disable order-approval --tenant acme takes it away again.

A workflow with Definition::required() is always active, in every tenant, without these steps.

The page Workflows is part of Access::pages() and appears in the navigation when the application has workflows. It lists the workflows of the panel’s resources that are available to the tenant:

Column Shows
Workflow name and the resource it belongs to; opens its settings
Status active, inactive or always active (required()); a badge when a draft waits for release
Version version of the definition and of the released configuration; cases still on an older version
Running, Total cases of the tenant
Schedule cases at risk and overdue (workflows with durations)
Open tasks open tasks and decisions
Last used time of the last transition

The figures are counted with one query per table, however many workflows there are. A workflow opens at /<panel>/workflows?workflow=<key> with its transitions, settings and the four eyes release.

The settings come from the definition:

Definition Setting of the tenant
Actor::slot('approver', default: …) who: the default, a role of the tenant, the creator, or a registered resolver
Transition::make('cancel')->optional() on or off
every workflow the durations, see below
Deadline::within(10) the working days of the deadline

The page also shows who runs which transition with the released configuration.

Every workflow offers its durations in working days (1–3650), also when the code defines none:

  • Default for every step – overrides defaultDuration(),
  • one field per state between the initial and the final states – overrides State::duration(),
  • Deadline of a case – only with Deadline::within().

An empty field keeps the value of the code (shown as placeholder). As soon as one duration is set, the cases of the workflow get a schedule: deadline, forecast, risk. How they are calculated: Durations and deadline.

By default, saved settings apply at once. Workflows whose settings must be checked by a second administrator say so in their definition:

$workflow->fourEyesRelease();

Then:

  1. An administrator saves the settings as draft (a new draft replaces an earlier one).
  2. Another administrator releases it. The author of a draft cannot release it.

Use it where a wrong setting has consequences (who approves payments, deadlines agreed with the works council) and the tenant has at least two administrators.

Either way, the released configuration applies at once, also to running records: open tasks are assigned again (e.g. a new approver gets the open approvals), due dates of the current steps and forecasts use the new durations. The deadlines of started cases stay.

Every configuration keeps its version; the history of a record names the configuration version each transition ran with. Drafts and releases are written to the audit log – also without four eyes, as draft and release by the same administrator (workflow.config_drafted, workflow.config_released), as are activation and deactivation.

$tenants = service('workflowTenants'); // in the current tenant
$workflow = new OrderWorkflow();
$tenants->makeAvailable($workflow);
$tenants->setActive($workflow, true);
$tenants->isAvailable($workflow);
$tenants->isActive($workflow);
$tenants->configuration($workflow); // ['values' => [...], 'version' => int|null]
$tenants->save($workflow, $values, 'user:1'); // true: applies at once; false: draft for a second administrator

In commands and seeders, run this inside service('tenantContext')->runAs($tenantId, fn () => …).