Fix pharmacy Call next to FIFO shared pool with one patient per counter.
Deploy Ladill Queue / deploy (push) Successful in 46s

Pharmacy/lab/billing now use a shared waiting pool claimed in order on Call next. A counter cannot stack another called ticket while one is already called or being served, so pharmacists no longer pull randomly or multiple patients at once.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-17 19:06:30 +00:00
co-authored by Cursor
parent 5356118fd8
commit d91f632544
3 changed files with 111 additions and 11 deletions
+32 -7
View File
@@ -101,6 +101,27 @@ class QueueEngine
public function callNext(ServiceQueue $queue, Counter $counter, ?string $actorRef = null): ?Ticket
{
// One patient at a time per service point: finish serving before calling another.
$activeServing = Ticket::query()
->where('service_queue_id', $queue->id)
->where('counter_id', $counter->id)
->where('status', 'serving')
->exists();
if ($activeServing) {
return null;
}
// Already called someone at this counter — re-announce instead of stacking another patient.
$alreadyCalled = Ticket::query()
->where('service_queue_id', $queue->id)
->where('counter_id', $counter->id)
->where('status', 'called')
->orderByDesc('called_at')
->first();
if ($alreadyCalled) {
return $this->recall($alreadyCalled, $actorRef);
}
$ticket = $this->nextWaitingTicket($queue, $counter);
if (! $ticket) {
return null;
@@ -111,14 +132,18 @@ class QueueEngine
public function callTicket(Ticket $ticket, Counter $counter, ?string $actorRef = null): Ticket
{
if ($ticket->assigned_counter_id && (int) $ticket->assigned_counter_id !== (int) $counter->id) {
$ticket->loadMissing('serviceQueue');
$queue = $ticket->serviceQueue;
if ($queue?->usesAssignedOnlyRouting()
&& $ticket->assigned_counter_id
&& (int) $ticket->assigned_counter_id !== (int) $counter->id) {
throw new \RuntimeException('Ticket is assigned to another service point.');
}
$ticket->update([
'status' => 'called',
'counter_id' => $counter->id,
'assigned_counter_id' => $ticket->assigned_counter_id ?: $counter->id,
'assigned_counter_id' => $counter->id,
'called_at' => now(),
]);
@@ -402,7 +427,8 @@ class QueueEngine
->where('service_queue_id', $queue->id)
->where('status', 'waiting')
->orderByRaw($this->priorityOrderSql())
->orderBy('issued_at');
->orderBy('issued_at')
->orderBy('id');
if (! $counter) {
return $query;
@@ -412,10 +438,9 @@ class QueueEngine
return $query->where('assigned_counter_id', $counter->id);
}
return $query->where(function ($q) use ($counter) {
$q->whereNull('assigned_counter_id')
->orWhere('assigned_counter_id', $counter->id);
});
// Shared pool (pharmacy, bank tellers, etc.): FIFO across the whole queue.
// Prior counter assignment does not block another desk from claiming the next waiting ticket.
return $query;
}
/**