Delegation & escalation
Two ways a pending step moves when the assigned approver is unavailable.
Delegation
An approver can hand a pending step to another user. The delegate can then act in place of the delegator on that step.
app(\Talivio\ApprovalFlows\ApprovalManager::class)
->delegate($approval, from: $manager, to: $deputy, comment: 'Out this week');
What happens:
- A
delegatedaction is recorded (withdelegated_to_id), so the audit trail shows the hand-off. - The delegate is notified with the same
ApprovalRequestedNotification. canAct()now returnstruefor the delegate on the current step, alongside the step's normal approvers.
Delegation can be disabled per step:
$flow->steps()->create([
// ...
'allow_delegation' => false,
]);
Delegating a step where allow_delegation is false throws ApprovalException::delegationNotAllowed().
Delegation is scoped to the current step. It does not carry forward to later steps, and it does not remove the original approvers — it adds the delegate.
Escalation
Escalation flags an approval that has sat on the same step too long and re-notifies its approvers. It is driven by a scheduled command, so you decide the cadence.
Configuration
// config/approval-flows.php
'escalation' => [
'enabled' => true,
'default_after_hours' => 48,
],
Each step may override the window with escalate_after_hours. ApprovalStep::escalationHours() returns the per-step value, or the config default, or null when escalation is disabled.
Scheduling the check
Add the bundled command to your scheduler. It escalates each qualifying approval once per step (it won't re-escalate the same step every run):
// routes/console.php (Laravel 11/12)
use Illuminate\Support\Facades\Schedule;
Schedule::command('approval-flows:check-escalations')->hourly();
When a step crosses its window, the command:
- records an
escalatedaction, - fires the
ApprovalEscalatedevent (with the approval and the step), - re-notifies the current approvers via
ApprovalEscalatedNotification.
Reacting to escalations
use Talivio\ApprovalFlows\Events\ApprovalEscalated;
Event::listen(ApprovalEscalated::class, function (ApprovalEscalated $e) {
// e.g. ping a manager in Slack, raise a ticket, bump a priority…
Log::warning('Approval stuck', [
'approval' => $e->approval->id,
'step' => $e->step->name,
]);
});
Escalation never changes the approval's status — it's a nudge, not a decision. The step still needs a real approve or reject to move on.
Next: Notifications.