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

Requests and state.

Read the state, stages, and relationships stored on an approval request.

An approval request runs one workflow for one record. It stores its own stages, assignments, and history. A closed request is never reopened. Revision creates a new request linked to the previous one.

Every request has a public ulid alongside its numeric key. It is sortable and safe to put in a URL, and it is what the console commands print.

Request states

use ByRcsc\LaravelApproval\Enums\RequestState;
StateMeaning
pendingWaiting on an approver
approvedEvery applicable stage was satisfied, and any draft was applied
rejectedThe rejection policy ended the request
returnedAn approver sent it back to the requester for revision
conflictedFully approved, but the record had moved, so nothing was applied
withdrawnThe requester withdrew it
cancelledAn administrator cancelled it, or the system closed it
supersededA conflicted request that was replaced by a revision

pending and conflicted are the unresolved states. Everything else is closed.

One unresolved request per record

A record has at most one unresolved request. Submitting a second one throws ApprovalInProgressException, which is the engine's answer to two people drafting conflicting edits: cancel the open request, or wait for it to close.

The package checks before opening the transaction and again while holding a row lock on the record. Two concurrent submissions therefore cannot both create an unresolved request.

Stage statuses

use ByRcsc\LaravelApproval\Enums\StageStatus;
StatusMeaning
queuedNot yet reached
activeCurrently waiting on its approvers
approvedIts approval rule was satisfied
rejectedIts rejection policy ended the request here
returnedAn approver returned the request from this stage
skippedIts condition did not pass, or the request ended before it ran

Stages are born queued; activating one is a transition of its own, and it is what sets the stage's deadline.

The timeline read model distinguishes the two ways a stage can be skipped, with a condition_skipped state for stages a condition turned off. See queries and timelines.

Lifecycle

submit ──▶ pending ──▶ stage 1 ──▶ stage 2 ──▶ …
                │                     │
                │                     ├─▶ rejected
                │                     └─▶ returned ──▶ revise ──▶ new request
                │
                └─▶ withdrawn / cancelled

all stages satisfied ──┬─▶ approved       (draft applied, or none held)
                       └─▶ conflicted     (the record moved; nothing applied)

A conflicted request has no completion timestamp and sends no approved notification. It waits for an administrator to reapply or discard the draft. See attribute drafts.

Models

ModelTableRole
ApprovalWorkflowapproval_workflowsEditable definition, soft-deletes
ApprovalWorkflowStageapproval_workflow_stagesOrdered stage of a definition
ApprovalWorkflowStageApproverapproval_workflow_stage_approversA concrete approver or resolver rule
ApprovalRequestapproval_requestsOne execution, with the public ULID
ApprovalRequestStageapproval_request_stagesA stage snapshot belonging to a request
ApprovalRequestAssignmentapproval_request_assignmentsOne approver's slot on a request stage
ApprovalActionapproval_actionsAppend-only, hash-chained history
ApprovalAttachmentapproval_attachmentsAppend-only evidence references

Each model reads its table name from approval.table_names, so renaming a table needs no code change.

Every model ships a factory under ByRcsc\LaravelApproval\Database\Factories, for building fixtures in application tests.

Relationships

$request->approvable;        // the record under approval (morph)
$request->requester;         // who submitted it (morph)
$request->workflow;          // reporting reference, nullable
$request->stages;            // ordered by sequence
$request->actions;           // ordered by id
$request->attachments;
$request->resubmittedFrom;   // the request this one replaces
$request->resubmissions;     // requests created by revising this one

On the approvable model, the Approvable concern adds:

$order->approvalRequests;        // every request, newest first
$order->latestApprovalRequest;   // the newest one

Closed requests are retained as approval history.

Asking a request about itself

$request->workflowSlug();          // the slug it is running under
$request->wasRequestedBy($user);   // did this user submit it

workflowSlug() always returns a string, which is the point: the workflow relationship is nullable, so $request->workflow->slug needs a guard every time you reach for it. When the workflow row is gone it returns a synthetic request-{id} rather than the original slug, so treat it as a stable label for display and grouping, not as a slug you can look a workflow up by.

wasRequestedBy() compares identity properly across morph types, which is what you want before offering a withdraw button. It answers a different question from authorization: see eligibility for whether the withdrawal would actually be allowed.

Deleting the record

The concern hooks model deletion:

  • A hard delete or force delete closes every unresolved request for the record, recording a system closure with the reason "The approvable model was deleted."
  • A soft delete leaves the request open by default, because a soft-deleted record is recoverable and so its request should be too. Override closeApprovalOnSoftDelete() to return true when your model should close them instead.

Actions and attachments use restrictOnDelete against the requests table: the trail outlives everything, and a request cannot be hard-deleted while its history exists.

Automatic submission

Creating a record can also create its request:

class PurchaseOrder extends Model
{
    use Approvable;

    public function shouldAutoSubmitForApproval(): bool
    {
        return true;
    }
}

Implementing the AutoSubmitsForApproval contract has the same effect. Override approvalAutoSubmission() to control the workflow, requester, comment, draft, and metadata used:

use ByRcsc\LaravelApproval\Data\AutoSubmission;

public function approvalAutoSubmission(): AutoSubmission
{
    return new AutoSubmission(
        workflow: 'purchase-order',
        requester: $this->buyer,
        comment: 'Raised automatically on creation.',
    );
}

The request is created after the record is persisted. It does not gate creation, and it is skipped when the record already has an unresolved request.

What to read next

  • Submitting for approval to create a request and its stage snapshots.
  • Recording decisions to move a pending request to its next state.
  • Attribute drafts to understand approved and conflicted draft outcomes.
  • Queries and timelines to load request progress and history for a screen.
PreviousApprovers and resolversNextConditional stages
View source

On this page

  1. Request states
  2. One unresolved request per record
  3. Stage statuses
  4. Lifecycle
  5. Models
  6. Relationships
  7. Asking a request about itself
  8. Deleting the record
  9. Automatic submission
  10. What to read next