›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-approval
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

byrcsc/laravel-approval · 1.x

SLAs and escalation.

Set stage deadlines, reminders, and actions for overdue approvals.

A stage with sla_minutes gets a deadline. The deadline counts from the moment the stage becomes active and starts waiting on its approvers, not from the moment the requester submitted the request.

$workflow->stages()->create([
    'sequence' => 1,
    'name' => 'Finance',
    'required_approvals' => 1,
    'sla_minutes' => 1440,
    'remind_every_minutes' => 240,
    'escalation_strategy' => EscalationAction::Escalate,
    'escalation_config' => ['resolver' => App\Org\ManagerOf::class],
]);

The package records deadlines whether or not anything acts on them.

Nothing escalates until the sweep runs. Schedule it:

Schedule::command('approval:escalate')->everyFifteenMinutes();

The interval sets how soon the sweep notices a late stage. It does not affect correctness. The sweep is idempotent, so running it every minute and running it every hour produce the same escalations at different resolutions.

Escalation actions

Each stage names one action, and the approval.escalation.strategies config map says which class runs it.

ActionShipped strategyBehavior
record_onlyRecordOnlyStrategyRecords the overdue state and emits the event
escalateEscalateStrategyAdds further approvers to the stage
auto_approveAutoDecideStrategyApproves the stage and advances the request
auto_rejectAutoDecideStrategyRejects the request

record_only is the default, and its strategy is empty on purpose. The engine marks the stage, fires StageOverdue, and writes the audit line before any strategy runs. By the time the engine calls RecordOnlyStrategy, all of the record-only behavior has already happened.

record_only sends no message. Turn on notifications or listen for the event to turn the recorded fact into a message. Deciding an approval automatically because it is late is a policy each organization chooses. A package should not assume it.

Adding approvers on a deadline

escalate resolves further approvers, typically the approver's manager or a duty officer:

'escalation_config' => [
    'resolver' => App\Org\ManagerOf::class,
    'resolver_config' => ['levels' => 1],
],

The resolver is an ordinary ApproverResolver. The engine skips people already represented on the stage and records the new assignments with the escalated source.

One rule needs care. A stage with a null required_approvals requires all of its assignments, so adding one would raise the number of approvals needed and make an already-late stage harder to finish. To avoid that, the engine pins a null rule to the assignment count the stage had before the addition. The added approvers act as alternatives to those who have not responded, not as extra requirements.

Set required_approvals inside escalation_config to pin a different number:

'escalation_config' => [
    'resolver' => App\Org\DutyOfficer::class,
    'required_approvals' => 1,
],

The engine leaves existing assignments in place. An approver who is only slow may still respond, and removing their assignment would discard work in progress.

ApproversEscalated fires after the assignments exist, so recipient calculation can only ever see the new holders.

Deciding automatically

auto_approve completes the stage and advances the request exactly as an approver's decision would. If the stage was the last one, the engine applies the draft with the same stale check. auto_reject closes the request. The requester may then revise and resubmit it.

Both record their action with no actor, because there was none. "The deadline passed and the stage was approved" is a different fact from "an approver approved it", and the history has to distinguish them. metadata carries automatic: true.

Override the recorded comment through the config:

'escalation_config' => ['comment' => 'Auto-approved under the 24-hour SLA.'],

Reminders

A stage with remind_every_minutes keeps emitting after it first goes overdue:

  • StageOverdue fires once, when the deadline first passes, before the strategy acts, so a listener sees the state that went overdue.
  • StageOverdueReminder fires at the configured cadence afterwards.

Reminders never rerun the strategy. escalated_at makes each strategy run exactly once. last_reminded_at paces the reminders. Each reminder writes a reminded audit action.

Custom strategies

Implement the contract and map an action to the class in the config:

use ByRcsc\LaravelApproval\Contracts\EscalationStrategy;
use ByRcsc\LaravelApproval\Enums\EscalationAction;

final class NotifyDutyManager implements EscalationStrategy
{
    public function handle(
        ApprovalRequest $request,
        ApprovalRequestStage $stage,
        EscalationAction $action,
        array $config,
    ): void {
        // ...
    }
}
// config/approval.php
'escalation' => [
    'strategies' => [
        'record_only' => NotifyDutyManager::class,
    ],
],

The engine resolves strategies from the container and runs them inside the transaction for that stage. If a strategy throws, the engine commits nothing about the stage, including the escalation mark, so the next run tries again. That retry is the right behavior for transient failures, which are the most common failures here.

$action names the action that called the strategy, so one class can handle several actions.

The package validates the configuration at boot. An unknown case, a missing class, or a class that does not implement the contract raises InvalidConfigurationException.

The sweep is not all-or-nothing

Each stage gets its own transaction, so one misconfigured stage cannot stop the sweep from handling the other four hundred. The command reports what happened and exits non-zero if anything threw:

php artisan approval:escalate
[
    'escalated' => ['12', '31'],   // stage ids
    'reminded' => ['8'],
    'stranded' => ['44'],
    'failed' => ['19' => 'Stage 3 escalates by bringing somebody in, but its escalation_config names no resolver to find them.'],
]

Approval::escalateOverdue() returns the same array, for applications that run the sweep from their own job.

Finding overdue work

$requests = Approval::overdue()->withProgress()->get();

overdue() returns pending requests whose active stage is past its deadline, independent of whether the escalation policy has run. A stage remains overdue until it is decided.

Stranded detection

The sweep also scans active stages whose living assignment holders can no longer meet the required approvals, marks them, and fires StageStranded. These stages are not overdue. Nobody can finish them, and no deadline will fix them. See delegation and reassignment.

What to read next

  • Approvers and resolvers to supply escalation approvers.
  • Delegation and reassignment to repair stranded stages.
  • Console commands to schedule and monitor the sweep.
PreviousDelegation and reassignmentNextEligibility and authorization
View source

On this page

  1. Escalation actions
  2. Adding approvers on a deadline
  3. Deciding automatically
  4. Reminders
  5. Custom strategies
  6. The sweep is not all-or-nothing
  7. Finding overdue work
  8. Stranded detection
  9. What to read next