›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-assignment
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Assignments and slots
  • Assigning and offering
  • Selection policies
  • Scopes and rotation
  • Profiles
  • The queue
  • Offer cascades

Operations

  • Expiry and scheduling
  • Reading assignments
  • Notifications
  • Events and listeners
  • Concurrency guarantees

Reference

  • Configuration
  • API reference
  • Console commands
  • Exceptions
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Assignments and slots
  • Assigning and offering
  • Selection policies
  • Scopes and rotation
  • Profiles
  • The queue
  • Offer cascades

Operations

  • Expiry and scheduling
  • Reading assignments
  • Notifications
  • Events and listeners
  • Concurrency guarantees

Reference

  • Configuration
  • API reference
  • Console commands
  • Exceptions
  • Testing
  • Troubleshooting

byrcsc/laravel-assignment · 1.x

Reading assignments.

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.

From the assignable

$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 first

Each 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 here

assignment() 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.

From the assignee

$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 type

workload() 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.

Query scopes

Assignment carries five scopes, each usable on any of the relations above:

ScopeMatches
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 everywhere

open() 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.

What a row carries

$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 it

The 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();

Reporting examples

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

What it does not do

  • Eager load for you. assignable and assignee are morph relations. Use with('assignee') when you are reading many rows.
  • Index your reporting queries. The table is indexed for the engine's own access paths: the open-slot constraint, assignee and status, assignable and creation time, status and expiry, and cascade id. A report that groups on something else may need its own index.
  • Expose the slot column. It is internal and hidden from array and JSON output.

What to read next

  • Assignments and slots for what the statuses mean.
  • API reference for the exact signature of every reader.
  • Events and listeners to react to changes instead of polling.
PreviousExpiry and schedulingNextNotifications
View source

On this page

  1. From the assignable
  2. From the assignee
  3. Query scopes
  4. What a row carries
  5. Reporting examples
  6. What it does not do
  7. What to read next