›
byrcsc/laravel-approval · 1.x
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');The package decides each request 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. One request that somebody else already decided should not undo the other forty.
$result->outcomes; // list<BulkDecisionOutcome>, in input order
$result->successful(); // only the ones that went through
$result->failed(); // only the ones that did notEach 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. For all-or-nothing
behavior, wrap the call in your own transaction and inspect failed() before
you commit.
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.
If the callback itself throws, the package records the exception as that request's failure, the same as an exception from the engine.
$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. It does not
exclude a request where the user recorded a rejection on a stage that is still
pending. A bulk decision on that request fails with DecisionAlreadyRecorded,
and the failure appears in failed() rather than as a thrown exception. Filter
further with any ordinary Eloquent constraint before you call the bulk method.
approveMany() takes an optional comment. rejectMany() requires one, exactly
as the single-request forms do. Both accept trailing metadata, which the
package records on every resulting audit action:
Approval::approveMany($requests, $user, 'Quarterly clearance', [
'batch' => $batchId,
]);The batch identifier then appears in each request's history. A later audit can use it to find the decisions that were made together.
Bulk decisions fire the same events as single ones, per request, inside that request's transaction. The package dispatches no bulk-specific event. A queued listener that dispatches after commit sees each request commit individually.
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.