Deploy Ladill Queue / deploy (push) Successful in 2m17s
Counters gain destination/staff metadata; tickets can be pre-assigned to a service point with assigned_only queues for healthcare while shared_pool preserves generic bank/government behavior. Display and announcements expose ticket, staff, and destination clearly for Care and public boards. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
6.1 KiB
PHP
164 lines
6.1 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\Organization;
|
|
use App\Models\ServiceQueue;
|
|
use App\Models\Ticket;
|
|
use App\Services\Qms\PlanService;
|
|
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,
|
|
protected PlanService $plans,
|
|
) {}
|
|
|
|
public function show(Request $request, Counter $counter): JsonResponse
|
|
{
|
|
$this->authorizeAbility($request, 'console.use');
|
|
$this->authorizeOwner($request, $counter);
|
|
$owner = $this->ownerRef($request);
|
|
$organization = $this->organization($request);
|
|
$canTransfer = $this->plans->hasFeature($organization, 'transfer');
|
|
|
|
$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) use ($counter) {
|
|
return [$queue->uuid => array_map(
|
|
fn ($t) => TicketPresenter::toArray($t),
|
|
$this->engine->waitingTickets($queue, 10, $counter),
|
|
)];
|
|
});
|
|
|
|
$allQueues = $canTransfer
|
|
? 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'])
|
|
: collect();
|
|
|
|
return response()->json([
|
|
'data' => [
|
|
'counter' => [
|
|
'uuid' => $counter->uuid,
|
|
'name' => $counter->name,
|
|
'destination' => $counter->displayDestination(),
|
|
'staff_display_name' => $counter->staff_display_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,
|
|
'routing_mode' => $q->routingMode(),
|
|
]),
|
|
'waiting_by_queue' => $waitingByQueue,
|
|
'transfer_queues' => $allQueues,
|
|
'can_transfer' => $canTransfer,
|
|
],
|
|
]);
|
|
}
|
|
|
|
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 {
|
|
$organization = Organization::query()->findOrFail($counter->organization_id);
|
|
abort_unless(
|
|
$this->plans->hasFeature($organization, 'transfer'),
|
|
403,
|
|
'Handing off tickets between queues requires Queue Pro.',
|
|
);
|
|
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);
|
|
}
|
|
}
|