›
byrcsc/laravel-assignment · 1.x
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.
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.
$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(); // falseNothing 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.
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 tradeYou 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.
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 nowThe 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 declinedAnother tradie calling accept() on that row gets NotTheAssignee.
$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 areworkload() counts active rows plus unanswered offers, so ten pending offers
cannot pile onto one idle tradie. LeastWorkload counts the same way.
$enquiry->autoAssign() takes no arguments.