›
byrcsc/laravel-assignment · 1.x
Read the current holder, the workload, and the full history from either side of an assignment.
Both traits read the same assignments table from their own side. An
assignable asks who has it; an assignee asks what it is holding.
$enquiry->assignment(); // the open assignment, or null
$enquiry->openAssignment(); // the same row, longer name
$enquiry->assignee(); // the assignee model, or null
$enquiry->isAssigned(); // is the slot filled
$enquiry->queuedAssignment(); // the queue entry, if it is waiting
$enquiry->assignments; // every row, oldest firstEach of them takes an optional role, and the role-less slot is its own slot:
$callout->assignee('crew');
$callout->isAssigned('supervisor');
$callout->assignment(); // the slot with no role, likely null hereassignment() and openAssignment() are the same method under two names. Both
return the row holding the slot, which is the active assignment when there is
one, or an unanswered offer that has not expired.
assignments is ordered oldest first by created_at and then by id, so the
relation reads as a timeline.
$crew->activeAssignments; // active rows only
$crew->openAssignments; // active plus unanswered offers
$crew->assignments; // every row this crew ever had
$crew->workload(); // how many are open
$crew->workload(Callout::class); // how many open, of one assignable typeworkload() counts the same rows as openAssignments, so an assignee holding
one callout and one unanswered offer has a workload of 2. That is the same
definition LeastWorkload uses, so a capacity filter you write agrees with the
policy the engine runs.
The type argument accepts a class name and honours the morph map, so it works
whether or not assignable_type stores aliases.
Unlike the assignable's relation, these are not ordered. Add your own
latest() or oldest() when order matters.
Assignment carries five scopes, each usable on any of the relations above:
| Scope | Matches |
|---|---|
open() | Active, or offered with no expiry or a future expiry |
active() | Status is active |
offered() | Status is offered, whether or not it is overdue |
ended() | Status is ended |
expired() | Offered, with an expiry that has passed |
$callout->assignments()->ended()->get(); // past holders
$crew->assignments()->expired()->count(); // offers this crew let lapse
Assignment::query()->open()->count(); // open work everywhereopen() and expired() both read expires_at, so they agree with the
timestamp rather than with the stored status. offered() is the raw status
check, which is what you want to find rows that are overdue but not yet ended.
$assignment->assignable; // back to the Enquiry, Callout, ...
$assignment->assignee; // the Tradie, Crew, ...
$assignment->assignedBy; // the actor, or null when the engine did it
$assignment->role; // the slot, or null
$assignment->status; // AssignmentStatus enum
$assignment->ended_reason; // EndReason enum, or null while open
$assignment->offered_at;
$assignment->accepted_at;
$assignment->expires_at;
$assignment->ended_at;
$assignment->scope; // which rotation produced itThe four timestamps are cast to CarbonImmutable. status and ended_reason
are backed enums, so comparisons are type safe:
use ByRcsc\LaravelAssignment\Enums\EndReason;
$assignment->ended_reason === EndReason::Declined;Five predicates read the state without touching the enums:
$assignment->isOpen();
$assignment->isActive();
$assignment->isOffered();
$assignment->isEnded();
$assignment->isExpired();Who currently holds work, by assignee:
use ByRcsc\LaravelAssignment\Models\Assignment;
Assignment::query()
->open()
->selectRaw('assignee_type, assignee_id, COUNT(*) as open_count')
->groupBy('assignee_type', 'assignee_id')
->get();How often offers are refused:
Assignment::query()
->ended()
->whereIn('ended_reason', [EndReason::Declined, EndReason::Expired])
->count();How long a callout waited for a crew:
$accepted = $callout->assignments()
->where('role', 'crew')
->whereNotNull('accepted_at')
->first();
$accepted?->created_at->diffInSeconds($accepted->accepted_at);assignable and assignee are morph relations. Use
with('assignee') when you are reading many rows.slot column. It is internal and hidden from array and JSON
output.