Files
ladill-queue/app/Http/Controllers/Api/TicketController.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

133 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\ServiceQueue;
use App\Models\Ticket;
use App\Services\Qms\QueueEngine;
use App\Services\Qms\TicketPresenter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TicketController extends Controller
{
use ScopesApiToAccount;
public function __construct(
protected QueueEngine $engine,
) {}
public function index(Request $request): JsonResponse
{
$this->authorizeAbility($request, 'tickets.view');
$owner = $this->ownerRef($request);
$organization = $this->organization($request);
$tickets = Ticket::owned($owner)
->where('organization_id', $organization->id)
->when($request->query('queue'), fn ($q, $uuid) => $q->whereHas('serviceQueue', fn ($sq) => $sq->where('uuid', $uuid)))
->when($request->query('status'), fn ($q, $status) => $q->where('status', $status))
->with(['serviceQueue', 'counter'])
->latest('issued_at')
->paginate(50);
return response()->json([
'data' => $tickets->getCollection()->map(fn (Ticket $t) => TicketPresenter::toArray($t)),
'meta' => [
'current_page' => $tickets->currentPage(),
'last_page' => $tickets->lastPage(),
'total' => $tickets->total(),
],
]);
}
public function store(Request $request): JsonResponse
{
$this->authorizeAbility($request, 'tickets.issue');
$owner = $this->ownerRef($request);
$validated = $request->validate([
'service_queue_id' => ['required', 'uuid'],
'customer_name' => ['nullable', 'string', 'max:255'],
'customer_phone' => ['nullable', 'string', 'max:50'],
'customer_email' => ['nullable', 'email', 'max:255'],
'priority' => ['nullable', 'string', 'in:'.implode(',', array_keys(config('qms.ticket_priorities')))],
'source' => ['nullable', 'string', 'in:'.implode(',', array_keys(config('qms.ticket_sources')))],
]);
$queue = ServiceQueue::where('uuid', $validated['service_queue_id'])->firstOrFail();
$this->authorizeOwner($request, $queue);
$ticket = $this->engine->issueTicket($queue, $owner, [
...$validated,
'source' => $validated['source'] ?? 'api',
'actor_ref' => $owner,
]);
return response()->json(['data' => TicketPresenter::toArray($ticket)], 201);
}
public function show(Request $request, Ticket $ticket): JsonResponse
{
$this->authorizeAbility($request, 'tickets.view');
$this->authorizeOwner($request, $ticket);
return response()->json(['data' => TicketPresenter::toArray($ticket->load(['serviceQueue', 'counter']))]);
}
public function callNext(Request $request, ServiceQueue $serviceQueue): JsonResponse
{
$this->authorizeAbility($request, 'console.use');
$this->authorizeOwner($request, $serviceQueue);
$validated = $request->validate([
'counter_id' => ['required', 'uuid'],
]);
$counter = \App\Models\Counter::where('uuid', $validated['counter_id'])->firstOrFail();
$this->authorizeOwner($request, $counter);
$ticket = $this->engine->callNext($serviceQueue, $counter, $this->ownerRef($request));
return response()->json(['data' => $ticket ? TicketPresenter::toArray($ticket) : null]);
}
public function action(Request $request, Ticket $ticket): JsonResponse
{
$this->authorizeAbility($request, 'tickets.manage');
$this->authorizeOwner($request, $ticket);
$actor = $this->ownerRef($request);
$validated = $request->validate([
'action' => ['required', 'string', 'in:recall,start,hold,skip,complete,no_show,cancel,delay,transfer'],
'minutes' => ['nullable', 'integer', 'min:5', 'max:120'],
'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),
'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),
'cancel' => $this->engine->cancel($ticket, $actor),
'delay' => $this->engine->delayArrival($ticket, (int) ($validated['minutes'] ?? 15), $actor),
default => $ticket,
};
return response()->json(['data' => TicketPresenter::toArray($result->load(['serviceQueue', 'counter']))]);
}
}