›
›
›
  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

Workflows and stages.

Define the ordered stages, approvers, and rules that an approval request follows.

A workflow describes one approval process. It has a name, a slug, and an ordered list of stages. Each stage defines its approvers and the rule for moving to the next stage.

Workflows and stages are Eloquent models. Your application can create and edit them through its own management screens.

use ByRcsc\LaravelApproval\Models\ApprovalWorkflow;

$workflow = ApprovalWorkflow::create([
    'name' => 'Purchase orders',
    'slug' => 'purchase-order',
    'description' => 'Finance, then the board above ₱100,000.',
    'active' => true,
]);

Workflow slugs use one global namespace. Shared-database multi-tenant applications must scope lookups themselves or include the tenant in each slug, for example acme:purchase-order.

Workflow columns

Column
nameDisplay name.
slugThe stable identifier callers name it by.
descriptionFree text for management screens.
activeAn inactive workflow refuses new submissions and is never chosen automatically.
approvable_typeRegisters the workflow against a model type, so submissions of that model can find it.
conditionA WorkflowCondition deciding whether this workflow applies to a submission.
condition_configConfiguration for that condition.
priorityOrders equally specific workflows; higher wins. Defaults to 0.
settingsPer-workflow overrides described below.

The approvable_type, condition, condition_config, and priority columns let one model use several workflows. You can leave them empty when the model has only one workflow. Choosing the workflow covers them in full.

Stage columns

$stage = $workflow->stages()->create([
    'sequence' => 1,
    'name' => 'Finance',
    'required_approvals' => 1,
    'rejection_policy' => RejectionPolicy::ApprovalImpossible,
    'sla_minutes' => 1440,
    'remind_every_minutes' => 240,
    'escalation_strategy' => EscalationAction::Escalate,
    'escalation_config' => ['resolver' => ManagerOf::class],
    'condition' => AmountExceeds::class,
    'condition_config' => ['threshold' => 100_000],
]);
ColumnMeaning
sequencePosition in the chain, positive and contiguous from one
nameDisplay name, carried onto the request snapshot
required_approvalsHow many approvals satisfy the stage; null means all assignments
rejection_policyWhen rejections end the request
sla_minutesDeadline, measured from the moment the stage becomes active
remind_every_minutesReminder cadence after the deadline passes
escalation_strategyWhat happens at the deadline
escalation_configConfiguration for that strategy
conditionA StageCondition deciding whether the stage applies at all
condition_configConfiguration for that condition

The database schema prevents two stages of one workflow from sharing a sequence.

Approval thresholds

required_approvals sets how many approvals a stage needs:

RuleConfiguration
1-of-Nrequired_approvals = 1
N-of-Mrequired_approvals = N
Unanimousrequired_approvals = null

A null value means every assignment must approve. The package counts the assignments after any approver resolvers run. Fluent definitions, array definitions, persisted rows, and request snapshots all treat an omitted value the same way.

If a threshold is higher than the number of assignments the stage can produce, the workflow can never reach a decision. Submission refuses such a workflow with InvalidWorkflowException before writing a row.

Rejection policy

The threshold says when a stage passes. The rejection policy says when it fails. You configure the two separately.

PolicyBehavior
approval_impossibleThe stage fails once the remaining approvers cannot reach the threshold
any_rejection_vetoesAny single rejection ends the request

approval_impossible is the default. On a 2-of-5 stage, it allows three rejections, and the fourth fails the stage. On a unanimous stage, it behaves like a veto, because one rejection already makes the threshold unreachable.

Rejection ends the whole request, not only the stage. The package marks later stages that never ran as skipped, not rejected. The requester may then revise and resubmit.

The snapshot boundary

Submission copies the following information onto the request:

  • the workflow's stages, in order, with every column above;
  • each stage's evaluated condition result; and
  • the approval assignments, with resolvers already run.

From then on, the request uses its own copy. The request's workflow_id is only a reporting reference. If someone deletes the workflow row, the database sets workflow_id to null instead of cascading the delete, so the history stays. A request submitted with an inline workflow has no workflow_id at all.

Because of the snapshot, you can edit a workflow at any time. Adding a stage, changing a threshold, or replacing an approver affects the next submission and no request already in progress. A request can also outlive the workflow that produced it.

If a reorganization must change requests already in progress, an administrator calls Approval::resync() as a separate, deliberate action.

Deactivating a workflow

$workflow->update(['active' => false]);

An inactive workflow refuses new submissions with InvalidWorkflowException. Requests already running do not change, because each one uses its own snapshot.

Workflows use soft deletes. Approval::workflow() no longer finds a deleted workflow, and its requests keep their history.

Looking a workflow up

use ByRcsc\LaravelApproval\Facades\Approval;

Approval::workflow('purchase-order');       // throws if missing
Approval::findWorkflow('purchase-order');   // null if missing
Approval::workflowFor($order);              // applies the resolution order
Approval::workflowsFor($order);             // every workflow that applies
Approval::workflows();                      // every active workflow

workflowFor() follows the same selection steps as submission. A workflow named as an argument always wins. Otherwise, the package gathers candidates from the model's approvalWorkflow(), the approval.workflows config map, and the workflows registered against the model's type. It then filters them by active state and condition.

When nothing applies, workflowFor() throws WorkflowNotFoundException. When two candidates are equally specific and have the same priority, it throws AmbiguousWorkflowException. See choosing the workflow.

Reading a workflow's configured approvers

$workflow = ApprovalWorkflow::query()
    ->with('stages.approvers.approver')
    ->where('slug', 'purchase-order')
    ->firstOrFail();

$approversByStage = $workflow->stages->mapWithKeys(
    fn ($stage) => [
        $stage->sequence => $stage->approvers->map(
            fn ($entry) => $entry->approver ?? $entry->resolver,
        ),
    ],
);

Each value is either an approver model or a resolver class-string. A resolver needs the record, requester, and stage configuration before it can name the approvers. Those inputs become available during submission.

Per-workflow settings

The settings JSON column holds per-workflow overrides of package configuration. The package reads one key:

$workflow->update(['settings' => ['allow_self_approval' => true]]);

The setting overrides approval.allow_self_approval for requests on this workflow. Unlike the stage rules, the package reads it live instead of from the snapshot. A tighter control policy should apply to requests already in progress.

What to read next

  • Choosing the workflow to select between several workflows for one model.
  • Approvers and resolvers to assign people, roles, or reporting lines.
  • Conditional stages to skip stages based on the proposed model values.
  • Submitting for approval to create requests from these workflow definitions.
PreviousQuick startNextChoosing the workflow
View source

On this page

  1. Workflow columns
  2. Stage columns
  3. Approval thresholds
  4. Rejection policy
  5. The snapshot boundary
  6. Deactivating a workflow
  7. Looking a workflow up
  8. Reading a workflow's configured approvers
  9. Per-workflow settings
  10. What to read next