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

Bulk decisions.

Decide several requests and inspect the result of each item.

Bulk operations use one naming convention, the Many suffix:

use ByRcsc\LaravelApproval\Facades\Approval;

$result = Approval::approveMany($requests, $financeUser);
$result = Approval::rejectMany($requests, $financeUser, 'Over budget');

Each request is decided in its own transaction, so one failure does not roll back the rest. A bulk approval over an approver's inbox is the common case, and one request that somebody else already decided should not undo the other forty.

Reading the result

$result->outcomes;      // list<BulkDecisionOutcome>, in input order
$result->successful();  // only the ones that went through
$result->failed();      // only the ones that did not

Each outcome carries the request it was built from, and either the resulting request or the exception that stopped it:

foreach ($result->failed() as $outcome) {
    report($outcome->exception);

    logger()->warning('Bulk approval skipped a request', [
        'request' => $outcome->request->ulid,
        'reason' => $outcome->exception::class,
    ]);
}

$outcome->successful() is the per-item boolean.

Bulk methods never throw for an individual request. A caller that wants all-or-nothing should wrap the call in its own transaction and inspect failed() before committing.

A different decision per request

decideMany() is the advanced form. The callback receives each request and returns the decision to record for it:

use ByRcsc\LaravelApproval\Data\ApprovalDecision;

$result = Approval::decideMany($requests, function ($request) use ($user, $limits) {
    return $request->approvable->amount <= $limits->autoApproveUnder
        ? ApprovalDecision::approve($user, 'Under the delegated limit')
        : ApprovalDecision::return($user, 'Exceeds the delegated limit');
});

approveMany() and rejectMany() are thin wrappers over it that build the same decision for every request.

An exception thrown by the callback itself is captured as that request's failure, the same as one thrown by the engine.

Driving it from the inbox

$requests = Approval::awaiting($user)
    ->whereHas('approvable', fn ($query) => $query->where('supplier_id', $supplier->id))
    ->get();

$result = Approval::approveMany($requests, $user, 'Bulk approved for a trusted supplier');

awaiting() excludes requests the user has already approved. A rejection they recorded on a stage that is still pending does not exclude it, so a bulk decision can still hit DecisionAlreadyRecorded, which lands in failed() rather than throwing. Filter further with any ordinary Eloquent constraint before calling.

Comments and metadata

approveMany() takes an optional comment; rejectMany() requires one, exactly as the single-request forms do. Both accept trailing metadata recorded on every resulting audit action:

Approval::approveMany($requests, $user, 'Quarterly clearance', [
    'batch' => $batchId,
]);

The batch identifier is then visible on each request's history, which is what lets a later audit reconstruct which decisions were made together.

Events

Bulk decisions fire the same events as single ones, per request, inside that request's transaction. Nothing bulk-specific is dispatched, and a queued listener that dispatches after commit sees each request commit individually.

Testing

Approval::fake() records a bulk call as one interaction:

$fake = Approval::fake();

Approval::approveMany($requests, $user);

$fake->assertApprovedMany();
$fake->assertApprovedMany(fn ($recorded) => count($recorded->target) === 3);

assertRejectedMany() is the rejection counterpart. The recorded interaction's target is the list of requests. See testing.

What to read next

  • Recording decisions for the eligibility and state rules applied to each request.
  • Queries and timelines to build the inbox supplied to a bulk action.
  • Testing to assert bulk decisions through the fake.
PreviousReturns and resubmissionNextDelegation and reassignment
View source

On this page

  1. Reading the result
  2. A different decision per request
  3. Driving it from the inbox
  4. Comments and metadata
  5. Events
  6. Testing
  7. What to read next