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

Queries and timelines.

Build approver inboxes, overdue lists, progress summaries, and timelines.

Facade queries

Each returns an Eloquent builder unless stated otherwise, so ordinary constraints, eager loads, and pagination all apply.

QueryReturns
Approval::awaiting($user)Pending requests whose active stage waits on them
Approval::overdue()Pending requests past their active stage's deadline
Approval::requestsFor($model)That model's request history, newest first
Approval::pendingFor($model)Its one unresolved request, or null
Approval::requesterFor($model)Who a submission would be attributed to, as a model
$inbox = Approval::awaiting($user)
    ->withProgress()
    ->paginate(20);

awaiting() excludes requests the user has already approved, which keeps a request out of their inbox while the stage waits on the remaining approvers. A delegated assignment belongs to the delegate rather than the delegating approver, so it appears in exactly one inbox.

requesterFor() returns a model rather than a builder. It answers the question submit() asks when no requester is passed. The method uses the record's approvalRequester() result first, then the authenticated user. It raises MissingRequesterException when neither answers, which makes it useful for failing early in a queued job or console command where there is no authenticated user to fall back on.

Request scopes

use ByRcsc\LaravelApproval\Models\ApprovalRequest;

ApprovalRequest::query()->pending();
ApprovalRequest::query()->unresolved();          // pending or conflicted
ApprovalRequest::query()->overdue();
ApprovalRequest::query()->forApprovable($order);
ApprovalRequest::query()->awaitingUser($user);
ApprovalRequest::query()->withProgress();

withProgress() eager-loads stages.assignments.approver, stages.assignments.delegatedTo, and stages.actions, everything the progress, approver, and timeline read models touch. Without it, rendering a list of requests will run those queries per row.

Model scopes

The Approvable concern adds scopes to the model under approval:

PurchaseOrder::query()->pendingApproval();
PurchaseOrder::query()->approvalApproved();
PurchaseOrder::query()->approvalRejected();
PurchaseOrder::query()->approvalReturned();
PurchaseOrder::query()->approvalWithdrawn();
PurchaseOrder::query()->approvalCancelled();
PurchaseOrder::query()->approvalConflicted();
PurchaseOrder::query()->approvalSuperseded();

PurchaseOrder::query()->withoutPendingApproval();
PurchaseOrder::query()->awaitingApprovalBy($user);

Two general forms take any number of states:

use ByRcsc\LaravelApproval\Enums\RequestState;

PurchaseOrder::query()->approvalState(RequestState::Rejected, RequestState::Returned);
PurchaseOrder::query()->everInApprovalState(RequestState::Rejected);

approvalState() asks about the latest request, the current-state question. everInApprovalState() searches the whole history, however long ago and whatever came after.

withoutPendingApproval() matches models with no unresolved request, neither pending nor conflicted.

Current stage and approvers

$request = Approval::pendingFor($order);

$request?->currentStage();            // the active ApprovalRequestStage
$request?->pendingApprovers();        // holders who have not responded
$request?->currentStageApprovers();   // every holder, responded or not

The model-side equivalents read through the model's own open request:

$order->currentApprovalStage();
$order->pendingApprovalApprovers();
$order->currentStageApprovers();
$order->isPendingApproval();

Each returns an empty collection, or null, when there is no unresolved request.

Both approver lists return the holder: the delegate where an assignment was delegated, the assigned approver otherwise.

Stage progress

$progress = $request->stageProgress();
PropertyMeaning
assignedApproversHow many assignments the stage has
decisionsRecordedHow many holders have responded, in any direction
approvalsReceivedHow many approved
approvalsRequiredThe threshold, with a null rule already resolved
remainingApprovalsHow many more are needed, floored at zero
rejectionsReceivedHow many rejected
possibleApprovalsThe best case still reachable
rejectionPolicyThe stage's policy
statusIts StageStatus
$progress?->isSatisfied();   // approvalsReceived >= approvalsRequired
$progress?->isRejected();    // per the stage's rejection policy

stageProgress() reads the stage the request points at with current_stage_sequence, and returns null when there is none.

Timelines

foreach ($request->timeline() as $entry) {
    $entry->stage;      // ApprovalRequestStage
    $entry->state;      // TimelineState
    $entry->approvers;  // Collection<Model> of assignment holders
    $entry->actions;    // Collection<ApprovalAction> for this stage
}

One entry per stage, in sequence order. timeline() eager-loads what it needs, so it is safe to call on a request loaded without withProgress(), though on a list of requests, loading progress up front is cheaper.

TimelineState adds one case to StageStatus:

StateMeaning
condition_skippedA condition turned the stage off at submission
skippedThe request ended before the stage ran

A timeline covers one request. Earlier and later requests in a linked chain keep their own, reachable through resubmittedFrom() and resubmissions().

$order->latestApprovalTimeline() is the model-side shorthand for the newest request's timeline.

Reconstructing a full chain

$timelines = collect();

for ($node = $order->latestApprovalRequest; $node !== null; $node = $node->resubmittedFrom) {
    $timelines->prepend(['request' => $node, 'entries' => $node->timeline()]);
}

Reading history directly

$request->actions;      // every audit action, oldest first
$request->attachments;  // every attachment reference

Actions carry action, comment, metadata, actor_label, and the hash chain. actor_label is the actor's readable name as captured when the line was written and never refreshed, so the trail still names them after the account is renamed or deleted. See audit trail and evidence.

What to read next

  • Requests and state for every request and stage state returned by these queries.
  • Eligibility and authorization to decide which actions an inbox should offer.
  • Audit trail and evidence for the actions behind a timeline.
PreviousEligibility and authorizationNextEvents and listeners
View source

On this page

  1. Facade queries
  2. Request scopes
  3. Model scopes
  4. Current stage and approvers
  5. Stage progress
  6. Timelines
  7. Reconstructing a full chain
  8. Reading history directly
  9. What to read next