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

Quick start.

Assign an enquiry to a tradie, rotate enquiries fairly across a team, then offer work the assignee can refuse.

This walkthrough routes enquiries to tradies three ways: by naming the assignee, by letting a policy pick one, and by offering the work so the tradie can decline it. It assumes Laravel Assignment is installed.

1. Prepare the models

use ByRcsc\LaravelAssignment\Concerns\Assignable;
use ByRcsc\LaravelAssignment\Concerns\Assignee;
use Illuminate\Database\Eloquent\Model;

class Enquiry extends Model
{
    use Assignable;

    protected $guarded = [];
}

class Tradie extends Model
{
    use Assignee;

    protected $guarded = [];
}

The enquiries table needs a trade, and the tradies table needs an available flag. Nothing else is required: assignment rows live in the package's own table.

2. Assign, read, and end

$enquiry = Enquiry::query()->create(['trade' => 'plumbing']);
$ada = Tradie::query()->create(['available' => true]);
$bo = Tradie::query()->create(['available' => true]);

$enquiry->assign($ada);
$enquiry->assignee();               // the Tradie Ada
$enquiry->isAssigned();             // true

$enquiry->reassign($bo);            // ends Ada's row, opens Bo's
$enquiry->unassign();               // ends Bo's row

$enquiry->assignments()->count();   // 2, the full history
$enquiry->isAssigned();             // false

Nothing was deleted. Both rows are still there, each carrying an ended_at and an ended_reason of reassigned and unassigned.

Calling assign() on a slot that already holds an open row throws SlotOccupied. Reassignment is a separate verb because replacing somebody is a different decision from filling an empty slot.

3. Rotate through a team

Naming the assignee does not scale past the first week. Hand the builder a candidate list and a policy instead:

use ByRcsc\LaravelAssignment\Facades\Assignment;
use ByRcsc\LaravelAssignment\Policies\RoundRobin;

$assignment = Assignment::for($enquiry)
    ->among(Tradie::query()->where('available', true)->get())
    ->scope('trade:'.$enquiry->trade)
    ->using(RoundRobin::class)
    ->assign();

$assignment->assignee;   // whichever tradie was next in this trade

You bring the candidates. The policy picks one of them.

The scope() string is where the rotation cursor lives. Every plumbing enquiry shares one cursor, so the plumbers take turns even though you rebuild the candidate list on every call. Electrical rotates on a cursor of its own.

Run the same block for six enquiries across two trades and each trade walks its own tradies in order. Rebuilding, reordering, growing, or shrinking the candidate list between calls does not break the rotation.

assign() returns null when the candidate list is empty or the policy picks nobody. It does not throw for that case.

4. Offer work instead of assigning it

Use offer() when the assignee gets a say:

use ByRcsc\LaravelAssignment\Policies\LeastWorkload;

$offer = Assignment::for($enquiry)
    ->among(Tradie::query()->where('available', true)->get())
    ->using(LeastWorkload::class)
    ->expiresIn(120)
    ->offer();

$offer->status;          // AssignmentStatus::Offered
$offer->expires_at;      // two minutes from now

The offer holds the slot while it stands. Nobody else can be assigned or offered that slot until it is answered or the two minutes pass.

Only the tradie the offer names can answer it:

$tradie = $offer->assignee;

$tradie->accept($offer);    // offered becomes active
// or
$tradie->decline($offer);   // the row ends with reason declined

Another tradie calling accept() on that row gets NotTheAssignee.

5. Read the state

$enquiry->assignment();              // the active assignment, or null
$enquiry->openAssignment();          // the active row, or the offer holding the slot
$enquiry->assignee();                // the assignee model, or null
$enquiry->assignments()->ended()->get();   // past holders, oldest first

$tradie->activeAssignments;       // what this tradie is holding
$tradie->openAssignments;         // active plus unanswered offers
$tradie->workload();              // how many of those there are

workload() counts active rows plus unanswered offers, so ten pending offers cannot pile onto one idle tradie. LeastWorkload counts the same way.

What to read next

  • Assignments and slots for roles and what "open" means.
  • Selection policies for the four shipped policies and writing your own.
  • Profiles to move the candidates, policy, and scope out of the call site so $enquiry->autoAssign() takes no arguments.
  • Offer cascades to pass a declined offer to the next candidate automatically.
PreviousInstallation and setupNextAssignments and slots
View source

On this page

  1. 1. Prepare the models
  2. 2. Assign, read, and end
  3. 3. Rotate through a team
  4. 4. Offer work instead of assigning it
  5. 5. Read the state
  6. What to read next