Files
ladill-queue/app/Http/Controllers/Qms/ConsoleController.php
T
isaacclad 7b2722f471
Deploy Ladill Queue / deploy (push) Successful in 1m19s
Gate ticket handoff as Pro; reorder settings cards.
Transfer between queues requires Pro (console UI, API, and plans copy).
Branches & team now sits after Organization on the settings page.
2026-07-16 09:54:53 +00:00

157 lines
6.0 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\PlanService;
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,
protected PlanService $plans,
) {}
public function show(Request $request, Counter $counter): View
{
$this->authorizeAbility($request, 'console.use');
$this->authorizeOwner($request, $counter);
$owner = $this->ownerRef($request);
$organization = $this->organization($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)];
});
$canTransfer = $this->plans->hasFeature($organization, 'transfer');
$allQueues = $canTransfer
? ServiceQueue::owned($owner)
->where('organization_id', $counter->organization_id)
->where('branch_id', $counter->branch_id)
->where('is_active', true)
->orderBy('name')
->get()
: collect();
return view('qms.console.show', compact(
'counter',
'queues',
'currentTicket',
'waitingByQueue',
'allQueues',
'canTransfer',
));
}
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'],
]);
if ($validated['action'] === 'transfer' && ! $this->plans->hasFeature($this->organization($request), 'transfer')) {
return redirect()
->route('qms.pro.index')
->with('error', 'Handing off tickets between queues requires Queue Pro.');
}
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.";
}
}