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

139 lines
5.4 KiB
PHP

<?php
namespace App\Http\Controllers\Qms;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Qms\Concerns\ScopesToAccount;
use App\Models\Counter;
use App\Models\ServiceQueue;
use App\Models\Ticket;
use App\Services\Qms\OrganizationResolver;
use App\Services\Qms\QueueEngine;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class ConsoleController extends Controller
{
use ScopesToAccount;
public function __construct(
protected QueueEngine $engine,
) {}
public function show(Request $request, Counter $counter): View
{
$this->authorizeAbility($request, 'console.use');
$this->authorizeOwner($request, $counter);
$owner = $this->ownerRef($request);
$counter->load(['serviceQueues', 'branch']);
$queues = $counter->serviceQueues;
$currentTicket = Ticket::owned($owner)
->where('counter_id', $counter->id)
->whereIn('status', ['called', 'serving', 'on_hold'])
->with('serviceQueue')
->latest('called_at')
->first();
$waitingByQueue = collect($queues)->mapWithKeys(function (ServiceQueue $queue) {
return [$queue->id => $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();
return view('qms.console.show', compact('counter', 'queues', 'currentTicket', 'waitingByQueue', 'allQueues'));
}
public function action(Request $request, Counter $counter): RedirectResponse
{
$this->authorizeAbility($request, 'console.use');
$this->authorizeOwner($request, $counter);
$actor = $this->ownerRef($request);
$validated = $request->validate([
'action' => ['required', 'string'],
'queue_id' => ['nullable', 'integer'],
'ticket_id' => ['nullable', 'integer'],
'to_queue_id' => ['required_if:action,transfer', 'nullable', 'integer'],
'reason' => ['nullable', 'string', 'max:500'],
]);
try {
$message = match ($validated['action']) {
'call_next' => $this->handleCallNext($counter, (int) $validated['queue_id'], $actor),
'recall' => $this->handleTicketAction($validated['ticket_id'], 'recall', $actor),
'start' => $this->handleTicketAction($validated['ticket_id'], 'start', $actor),
'hold' => $this->handleTicketAction($validated['ticket_id'], 'hold', $actor),
'skip' => $this->handleTicketAction($validated['ticket_id'], 'skip', $actor),
'complete' => $this->handleTicketAction($validated['ticket_id'], 'complete', $actor),
'no_show' => $this->handleTicketAction($validated['ticket_id'], 'no_show', $actor),
'transfer' => $this->handleTransfer($validated['ticket_id'], (int) $validated['to_queue_id'], $counter, $validated['reason'] ?? null, $actor),
'available' => tap(null, fn () => $counter->update(['status' => 'available'])),
'offline' => tap(null, fn () => $counter->update(['status' => 'offline'])),
default => null,
};
} catch (\RuntimeException $e) {
return back()->with('error', $e->getMessage());
}
return $message
? back()->with('success', $message)
: back();
}
protected function handleCallNext(Counter $counter, int $queueId, string $actor): ?string
{
$queue = ServiceQueue::findOrFail($queueId);
$ticket = $this->engine->callNext($queue, $counter, $actor);
return $ticket ? "Called {$ticket->ticket_number}." : 'No tickets waiting in that queue.';
}
protected function handleTicketAction(?int $ticketId, string $action, string $actor): ?string
{
if (! $ticketId) {
return null;
}
$ticket = Ticket::findOrFail($ticketId);
match ($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),
default => null,
};
return null;
}
protected function handleTransfer(?int $ticketId, int $toQueueId, Counter $counter, ?string $reason, string $actor): ?string
{
if (! $ticketId || ! $toQueueId) {
return null;
}
$ticket = Ticket::findOrFail($ticketId);
$toQueue = ServiceQueue::findOrFail($toQueueId);
if (
$toQueue->organization_id !== $counter->organization_id
|| $toQueue->branch_id !== $counter->branch_id
|| $toQueue->owner_ref !== $counter->owner_ref
) {
throw new \RuntimeException('You can only hand off to queues at this branch.');
}
$this->engine->transfer($ticket, $toQueue, $counter, $reason, $actor);
return "Handed off {$ticket->ticket_number} to {$toQueue->name}. Ticket number unchanged.";
}
}