›
byrcsc/laravel-approval · 1.x
Return a request for revision and submit a linked replacement.
A returned request is waiting on its requester rather than on an approver:
Approval::returnForRevision($request, $financeUser, 'Attach the supplier quote');The request state becomes returned, and ApprovalReturned fires with the
stage, the actor, and the reason. The engine marks the deciding stage
returned and the stages that never ran skipped. The attempt is over.
Nothing resubmits automatically. The requester decides what to do next.
use ByRcsc\LaravelApproval\Data\RevisedDraft;
$replacement = Approval::reviseAndResubmit($request, RevisedDraft::withAttributes([
'amount' => 8_000,
]));The package does not reopen or edit the old request. The old request stays as
the record of what happened. The replacement is a new request. It starts
from the first applicable stage of a fresh workflow snapshot, and
resubmitted_from_id links the two requests.
$replacement->resubmittedFrom; // the request it replaces
$request->resubmissions; // every replacement raised from this requestresubmissions is a list rather than a single record, because a replacement
can itself be returned or rejected and revised again.
The stages that approved the first attempt approved that version of it. A revision is a different proposal, so the package asks those stages again on purpose.
Because the snapshot is fresh, a workflow edited since the original submission takes effect on the replacement. The engine also re-evaluates conditions against the new proposal, so a smaller revised amount can skip a board stage the original triggered.
| State | Revisable |
|---|---|
returned | yes |
rejected | yes |
withdrawn | yes |
cancelled | yes |
conflicted | yes |
approved | no |
pending | no |
superseded | no |
approved is not revisable on purpose, because an approved request succeeded.
To change the record again, make an ordinary new submission, not a revision of
an old one.
Anything else raises AlreadyDecidedException::notRevisable().
Revising a conflicted request marks it superseded and records a
superseded action naming the replacement. Superseding also clears the
unresolved request. Without that step, the package could not submit the
replacement at all.
RevisedDraft has a named constructor for each choice, because null cannot
tell the choices apart:
RevisedDraft::withAttributes(['amount' => 8_000]); // a revised draft
RevisedDraft::inherit(); // carry the old one forward
RevisedDraft::none(); // record-level replacement| Constructor | Result |
|---|---|
withAttributes() | Replaces the previous draft entirely. Keys may be added, changed, or left out |
inherit() | Carries the previous draft forward unchanged |
none() | Submits the record itself rather than an edit to it |
inherit() on a request that held no draft produces a record-level
replacement.
The package revalidates revised attributes against the model's
approvableAttributes(), exactly as it does for an original submission.
The replacement inherits both from the request being revised unless you pass them:
Approval::reviseAndResubmit(
$request,
RevisedDraft::inherit(),
requester: $delegateBuyer,
workflow: 'purchase-order-expedited',
);A request with no stored workflow has nothing to inherit. A request has no
stored workflow when someone deleted its workflow, or when it ran under an
inline workflow. For such a request, pass a
workflow or the inline definition again. Otherwise, resubmission throws
WorkflowNotFoundException.
ApprovalResubmitted fires with the replacement as $request and the original
as $previous. The replacement's trail opens as every submission does, with a
submitted action, followed by a resubmitted action naming the original's
ULID. A superseded original gets a matching superseded action naming the
replacement.
Each request keeps its own timeline. Walk resubmittedFrom() to reconstruct
the full chain:
$chain = collect();
for ($node = $replacement; $node !== null; $node = $node->resubmittedFrom) {
$chain->push($node);
}Both close the current attempt and both can be revised. The difference is what they say:
Because the two states differ, a report can separate "sent back for a missing attachment" from "declined".