Fix queue handoff so tickets keep their number across services.
Deploy Ladill Queue / deploy (push) Successful in 40s

Handoff now frees the counter, ends the service session, and requeues
the same ticket for the next department — critical for hospital flows
like doctor → lab.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-14 21:22:01 +00:00
co-authored by Cursor
parent d938acf909
commit d2f5ff11d2
12 changed files with 333 additions and 74 deletions
@@ -87,7 +87,7 @@ class ConsoleApiController extends Controller
'call_next' => $this->handleCallNext($counter, $validated['queue_uuid'] ?? '', $actor),
'available' => tap($counter)->update(['status' => 'available']),
'offline' => tap($counter)->update(['status' => 'offline']),
default => $this->handleTicketAction($validated, $actor),
default => $this->handleTicketAction($validated, $counter, $actor),
};
if ($result instanceof Ticket) {
@@ -105,7 +105,7 @@ class ConsoleApiController extends Controller
return $this->engine->callNext($queue, $counter, $actor);
}
protected function handleTicketAction(array $validated, string $actor): Ticket|Counter|null
protected function handleTicketAction(array $validated, Counter $counter, string $actor): Ticket|Counter|null
{
$ticketUuid = $validated['ticket_uuid'] ?? '';
abort_if($ticketUuid === '', 422, 'ticket_uuid is required for this action.');
@@ -119,14 +119,28 @@ class ConsoleApiController extends Controller
'skip' => $this->engine->skip($ticket, $actor),
'complete' => $this->engine->completeTicket($ticket, $actor),
'no_show' => $this->engine->markNoShow($ticket, $actor),
'transfer' => $this->engine->transfer(
'transfer' => $this->handleTransfer(
$ticket,
ServiceQueue::where('uuid', $validated['to_queue_uuid'] ?? '')->firstOrFail(),
null,
$counter,
$validated['to_queue_uuid'] ?? '',
$validated['reason'] ?? null,
$actor,
),
default => abort(422, 'Unknown action.'),
};
}
protected function handleTransfer(
Ticket $ticket,
Counter $counter,
string $toQueueUuid,
?string $reason,
string $actor,
): Ticket {
abort_if($toQueueUuid === '', 422, 'to_queue_uuid is required for transfer.');
$toQueue = ServiceQueue::where('uuid', $toQueueUuid)->firstOrFail();
return $this->engine->transfer($ticket, $toQueue, $counter, $reason, $actor);
}
}
+10 -7
View File
@@ -101,12 +101,20 @@ class TicketController extends Controller
$actor = $this->ownerRef($request);
$validated = $request->validate([
'action' => ['required', 'string', 'in:recall,start,hold,skip,complete,no_show,cancel,delay'],
'action' => ['required', 'string', 'in:recall,start,hold,skip,complete,no_show,cancel,delay,transfer'],
'minutes' => ['nullable', 'integer', 'min:5', 'max:120'],
'to_queue_id' => ['nullable', 'uuid'],
'to_queue_id' => ['required_if:action,transfer', 'nullable', 'uuid'],
'reason' => ['nullable', 'string', 'max:500'],
]);
if ($validated['action'] === 'transfer') {
$toQueue = ServiceQueue::where('uuid', $validated['to_queue_id'])->firstOrFail();
$this->authorizeOwner($request, $toQueue);
$result = $this->engine->transfer($ticket, $toQueue, null, $validated['reason'] ?? null, $actor);
return response()->json(['data' => TicketPresenter::toArray($result->load(['serviceQueue', 'counter']))]);
}
$result = match ($validated['action']) {
'recall' => $this->engine->recall($ticket, $actor),
'start' => $this->engine->startServing($ticket, $actor),
@@ -119,11 +127,6 @@ class TicketController extends Controller
default => $ticket,
};
if ($validated['action'] === 'transfer' && ! empty($validated['to_queue_id'])) {
$toQueue = ServiceQueue::where('uuid', $validated['to_queue_id'])->firstOrFail();
$result = $this->engine->transfer($ticket, $toQueue, null, $validated['reason'] ?? null, $actor);
}
return response()->json(['data' => TicketPresenter::toArray($result->load(['serviceQueue', 'counter']))]);
}
}