›
›
›
  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 query returns an Eloquent builder unless the table says 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. A request leaves the user's inbox once they approve, even 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 returns the requester that submit() uses when you pass no requester. The method uses the record's approvalRequester() result first, then the authenticated user. It raises MissingRequesterException when neither gives a requester. Call it to fail early in a queued job or console command, where there is no authenticated user.

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. The progress, approver, and timeline read models use those relations. Without withProgress(), a list of requests runs those queries once 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);

approvalState() and everInApprovalState() take any number of states:

use ByRcsc\LaravelApproval\Enums\RequestState;

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

approvalState() checks the latest request, so it tells you the record's current state. everInApprovalState() searches the whole history and matches a state from any request, whatever happened after it.

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 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 of each assignment. The holder is the delegate if the assignment was delegated, and 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
}

timeline() returns one entry per stage, in sequence order. It eager-loads what it needs, so you can call it on a request loaded without withProgress(). For 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 have their own timelines. Reach them 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 at the time the action was written. The package never refreshes it, so the trail still names the actor 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