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

Returns and resubmission.

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.

Revising and resubmitting

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 request

resubmissions is a list rather than a single record, because a replacement can itself be returned or rejected and revised again.

Starting at stage one is deliberate

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.

Which states can be revised

StateRevisable
returnedyes
rejectedyes
withdrawnyes
cancelledyes
conflictedyes
approvedno
pendingno
supersededno

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.

Choosing the new draft

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
ConstructorResult
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.

Requester and workflow

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.

Events and history

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);
}

Returning versus rejecting

Both close the current attempt and both can be revised. The difference is what they say:

  • Return means the proposal is fixable and the requester should fix it. The state reads as "waiting on the requester", and the shipped notification tells them so.
  • Reject means the proposal is refused. Revising is still possible, but nothing in the package suggests it.

Because the two states differ, a report can separate "sent back for a missing attachment" from "declined".

What to read next

  • Attribute drafts to choose the draft carried into the replacement request.
  • Submitting for approval for the validation applied to the new request.
  • Requests and state to follow the links between attempts.
PreviousAttribute draftsNextBulk decisions
View source

On this page

  1. Revising and resubmitting
  2. Starting at stage one is deliberate
  3. Which states can be revised
  4. Choosing the new draft
  5. Requester and workflow
  6. Events and history
  7. Returning versus rejecting
  8. What to read next