Files
ladill-queue/app/Http/Controllers/Api/ConsoleApiController.php
T
isaaccladandCursor d2f5ff11d2
Deploy Ladill Queue / deploy (push) Successful in 40s
Fix queue handoff so tickets keep their number across services.
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>
2026-07-14 21:22:01 +00:00

147 lines
5.3 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Api\Concerns\ScopesApiToAccount;
use App\Http\Controllers\Controller;
use App\Models\Counter;
use App\Models\ServiceQueue;
use App\Models\Ticket;
use App\Services\Qms\QueueEngine;
use App\Services\Qms\TicketPresenter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ConsoleApiController extends Controller
{
use ScopesApiToAccount;
public function __construct(
protected QueueEngine $engine,
) {}
public function show(Request $request, Counter $counter): JsonResponse
{
$this->authorizeAbility($request, 'console.use');
$this->authorizeOwner($request, $counter);
$owner = $this->ownerRef($request);
$counter->load(['serviceQueues', 'branch']);
$currentTicket = Ticket::owned($owner)
->where('counter_id', $counter->id)
->whereIn('status', ['called', 'serving', 'on_hold'])
->with('serviceQueue')
->latest('called_at')
->first();
$waitingByQueue = collect($counter->serviceQueues)->mapWithKeys(function (ServiceQueue $queue) {
return [$queue->uuid => array_map(
fn ($t) => TicketPresenter::toArray($t),
$this->engine->waitingTickets($queue, 10),
)];
});
$allQueues = ServiceQueue::owned($owner)
->where('organization_id', $counter->organization_id)
->where('branch_id', $counter->branch_id)
->where('is_active', true)
->orderBy('name')
->get(['uuid', 'name', 'prefix']);
return response()->json([
'data' => [
'counter' => [
'uuid' => $counter->uuid,
'name' => $counter->name,
'status' => $counter->status,
'branch' => $counter->branch?->name,
],
'current_ticket' => $currentTicket ? TicketPresenter::toArray($currentTicket) : null,
'queues' => $counter->serviceQueues->map(fn (ServiceQueue $q) => [
'uuid' => $q->uuid,
'name' => $q->name,
'prefix' => $q->prefix,
]),
'waiting_by_queue' => $waitingByQueue,
'transfer_queues' => $allQueues,
],
]);
}
public function action(Request $request, Counter $counter): JsonResponse
{
$this->authorizeAbility($request, 'console.use');
$this->authorizeOwner($request, $counter);
$actor = $this->ownerRef($request);
$validated = $request->validate([
'action' => ['required', 'string'],
'queue_uuid' => ['nullable', 'uuid'],
'ticket_uuid' => ['nullable', 'uuid'],
'to_queue_uuid' => ['nullable', 'uuid'],
'reason' => ['nullable', 'string', 'max:500'],
]);
$result = match ($validated['action']) {
'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, $counter, $actor),
};
if ($result instanceof Ticket) {
return response()->json(['data' => TicketPresenter::toArray($result->load(['serviceQueue', 'counter']))]);
}
return $this->show($request, $counter->fresh());
}
protected function handleCallNext(Counter $counter, string $queueUuid, string $actor): ?Ticket
{
abort_if($queueUuid === '', 422, 'queue_uuid is required.');
$queue = ServiceQueue::where('uuid', $queueUuid)->firstOrFail();
return $this->engine->callNext($queue, $counter, $actor);
}
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.');
$ticket = Ticket::where('uuid', $ticketUuid)->firstOrFail();
return match ($validated['action']) {
'recall' => $this->engine->recall($ticket, $actor),
'start' => $this->engine->startServing($ticket, $actor),
'hold' => $this->engine->hold($ticket, $actor),
'skip' => $this->engine->skip($ticket, $actor),
'complete' => $this->engine->completeTicket($ticket, $actor),
'no_show' => $this->engine->markNoShow($ticket, $actor),
'transfer' => $this->handleTransfer(
$ticket,
$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);
}
}