›
byrcsc/laravel-assignment · 1.x
Laravel Assignment routes work to people and teams, and records who held what.
An enquiry needs a tradie. A callout needs a crew. A lead needs a sales agent. A ticket needs whoever is on shift. Two requests arrive at once and both pick the same person, so one of them overwrites the other without an error. Nobody can answer who held the record last week, because the second write also replaced the only column that recorded the answer.
Laravel Assignment gives you the slot and the history. Any Eloquent model can be assigned, and any Eloquent model can take assignments. A database constraint, not an application check, allows at most one open assignment per slot.
$enquiry->assign($tradie); // active immediately
$enquiry->assignee(); // the Tradie, or null
$enquiry->reassign($otherTradie); // ends the old row, opens the new one
$enquiry->assignments; // every row, oldest firstYour application keeps ownership of its users, its teams, its eligibility rules, and whatever being assigned actually means.
| Requirement | Supported versions |
|---|---|
| PHP | 8.3, 8.4 |
| Laravel | 12.x, 13.x |
| Databases | MySQL, PostgreSQL, SQLite |
The package follows semantic versioning, so upgrading within 1.x is safe.
Source and issues live on
GitHub.
An assignable is anything that gets assigned: an enquiry, an order, a
ticket, a callout. Add the Assignable trait to that model.
An assignee is anything that takes an assignment: a user, a crew, a team.
Add the Assignee trait to that model.
A role is an optional label that lets one assignable hold more than one assignee at a time. A callout can hold a crew and a tradie at once, because each role is its own slot. Leave the role off and an assignable has one slot.
assign(), which writes an active assignment, and offer(), which writes
one that the assignee has to accept or decline.RoundRobin, LeastWorkload, Random, and FirstAvailable selection
policies, plus any closure, behind one SelectionPolicy contract.The package leaves the following out by design. None of it is planned work.
assign() and offer() are ordinary method calls with no
policy behind them.These design decisions shape the rest of the package.
The open-slot rule is a database constraint. An internal slot column
holds the role while a row is open and null once it ends, under a unique index
on the assignable and that column. The loser of a race gets an exception, never
a duplicate. A check in PHP could not stop two concurrent requests from both
writing an open row.
The package ends assignments and never deletes them. An ended row records
ended_at and an ended_reason of unassigned, reassigned, declined,
expired, or completed. The rows are the history, and there is no separate
audit table.
Timestamps decide expiry. An offer whose expires_at has passed stops
holding its slot immediately. assignment:tick advances cascades and flushes
the queue. Reads are correct whether or not the command has run.
Eligibility and selection are separate jobs. Your application answers who can take the work. The engine answers who gets it, among the candidates you passed, fairly and atomically.