›
byrcsc/laravel-assignment · 1.x
A declined or expired offer moves to the next candidate, skipping everyone who already turned it down.
When nobody answers an offer, the work waits. A cascade passes the work on. When the assignee declines an offer or the offer expires, the engine runs the selection again against the remaining candidates and offers the work to the next one.
offer to Eli -> declined
offer to Fay -> expired
offer to Ivo -> acceptedEach step is a new row, so the assignable's history shows every crew who was asked and what they did.
A cascade has to resolve candidates again at each step. Only a profile can supply them at that point:
autoAssign() on a model in offer mode gets a
cascade_id and cascades.$model->offer($assignee) has
no cascade_id. It ends when the assignee declines it or it expires, and
nothing follows.The package does this by design. An ad-hoc offer has no candidate list to fall back on, and guessing one would be worse than stopping.
Re-offers exclude every assignee who declined or let an offer expire in the
same cascade. The engine tracks the exclusion through the cascade_id on the
rows, so the exclusion still applies when the candidate list changes between
steps.
The exclusion does not persist beyond the cascade. Once the cascade ends, a later attempt starts fresh with everyone eligible again, including the people who said no last time.
The cascade stops. Then:
queueWhenEmpty() returns true, the engine queues the
assignable.OffersExhausted fires with the last offer.public function handle(OffersExhausted $event): void
{
$event->lastOffer->assignable; // what nobody would take
$event->lastOffer->role;
}Exhaustion means that your candidate pool is too small, that everybody is busy, or that nobody wants the work. Put any escalation in a listener.
A later flush starts a new cascade with a new id, so the people who declined the first round are asked again.
A tick can exhaust and re-offer in one run.
assignment:tickexpires overdue offers first and flushes the queue second. If an offer exhausts its cascade during the expiry step, the engine queues the assignable. The flush in the same command can then start a fresh cascade at once. That cascade can re-offer the work to somebody who declined moments earlier. If you do not want that, split the two steps.
A decline advances the cascade synchronously, inside the call:
$crew->decline($offer);
$callout->openAssignment('crew'); // already the next crew's offerThe assignee who declined gets no "assignment removed" notification, because they chose not to take it. The next candidate gets an "offer received" notification as usual.
An expired offer stops holding its slot the moment expires_at passes. The
row still reads offered until something ends it. Until then, the cascade
does not move to the next candidate.
The tick and delayed expiry jobs advance the cascade:
// routes/console.php
Schedule::command('assignment:tick')->everyMinute();// config/assignment.php
'dispatch_expiry_jobs' => true,The tick processes every overdue offer. The job option also dispatches one delayed job per offer, set to run when the offer expires. The delayed job gives second-level precision instead of cron granularity. The tick still handles any jobs that were lost.
The package dispatches delayed jobs only for offers that have both a
cascade_id and an expires_at, so an ad-hoc offer never queues one. If the
assignee answers the offer before the job runs, the job does nothing.
OffersExhausted and the rest.