authorizeAbility($request, 'queues.view'); $owner = $this->ownerRef($request); $organization = $this->organization($request); $queues = ServiceQueue::owned($owner) ->where('organization_id', $organization->id) ->with(['branch', 'department']) ->orderBy('name') ->get() ->map(fn (ServiceQueue $q) => $this->serializeQueue($q)); return response()->json(['data' => $queues]); } public function store(Request $request): JsonResponse { $this->authorizeAbility($request, 'queues.manage'); $owner = $this->ownerRef($request); $organization = $this->organization($request); $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:1000'], 'branch_id' => ['nullable', 'integer'], 'branch_name' => ['nullable', 'string', 'max:255'], 'department_id' => ['nullable', 'integer'], 'color' => ['nullable', 'string', 'max:20'], 'prefix' => ['required', 'string', 'max:10'], 'strategy' => ['nullable', 'string', 'in:'.implode(',', array_keys(config('qms.queue_strategies')))], 'avg_service_seconds' => ['nullable', 'integer', 'min:60', 'max:7200'], 'max_capacity' => ['nullable', 'integer', 'min:1'], 'is_active' => ['nullable', 'boolean'], 'external_key' => ['nullable', 'string', 'max:191'], ]); $branch = $this->resolveBranch($organization->id, $owner, $validated); abort_unless($branch, 422, 'A valid branch_id or branch_name is required.'); $externalKey = isset($validated['external_key']) ? trim((string) $validated['external_key']) : ''; if ($externalKey !== '') { $existing = ServiceQueue::owned($owner) ->where('organization_id', $organization->id) ->where('settings->external_key', $externalKey) ->first(); if ($existing) { $existing->update([ 'name' => $validated['name'], 'description' => $validated['description'] ?? $existing->description, 'branch_id' => $branch->id, 'department_id' => $validated['department_id'] ?? $existing->department_id, 'color' => $validated['color'] ?? $existing->color, 'prefix' => $validated['prefix'], 'strategy' => $validated['strategy'] ?? $existing->strategy, 'avg_service_seconds' => $validated['avg_service_seconds'] ?? $existing->avg_service_seconds, 'max_capacity' => $validated['max_capacity'] ?? $existing->max_capacity, 'is_active' => array_key_exists('is_active', $validated) ? (bool) $validated['is_active'] : true, 'settings' => array_merge($existing->settings ?? [], ['external_key' => $externalKey]), ]); AuditLogger::record($owner, 'service_queue.updated', $organization->id, $owner, ServiceQueue::class, $existing->id); return response()->json(['data' => $this->serializeQueue($existing->fresh(['branch', 'department']))]); } } // Fall back to name + branch match so Care/manual admin create do not duplicate. $byName = ServiceQueue::owned($owner) ->where('organization_id', $organization->id) ->where('branch_id', $branch->id) ->whereRaw('LOWER(name) = ?', [strtolower($validated['name'])]) ->first(); if ($byName) { $settings = $byName->settings ?? []; if ($externalKey !== '') { $settings['external_key'] = $externalKey; } $byName->update([ 'prefix' => $validated['prefix'], 'strategy' => $validated['strategy'] ?? $byName->strategy, 'is_active' => array_key_exists('is_active', $validated) ? (bool) $validated['is_active'] : true, 'settings' => $settings !== [] ? $settings : $byName->settings, ]); AuditLogger::record($owner, 'service_queue.updated', $organization->id, $owner, ServiceQueue::class, $byName->id); return response()->json(['data' => $this->serializeQueue($byName->fresh(['branch', 'department']))]); } $currentCount = ServiceQueue::owned($owner)->where('organization_id', $organization->id)->count(); abort_unless( app(PlanService::class)->canAddQueue($organization, $currentCount), 422, 'Queue limit reached for current plan.', ); $queue = ServiceQueue::create([ 'owner_ref' => $owner, 'organization_id' => $organization->id, 'branch_id' => $branch->id, 'department_id' => $validated['department_id'] ?? null, 'name' => $validated['name'], 'description' => $validated['description'] ?? null, 'color' => $validated['color'] ?? '#4f46e5', 'prefix' => $validated['prefix'], 'strategy' => $validated['strategy'] ?? 'fifo', 'avg_service_seconds' => $validated['avg_service_seconds'] ?? 300, 'max_capacity' => $validated['max_capacity'] ?? null, 'is_active' => array_key_exists('is_active', $validated) ? (bool) $validated['is_active'] : true, 'settings' => $externalKey !== '' ? ['external_key' => $externalKey] : null, ]); AuditLogger::record($owner, 'service_queue.created', $organization->id, $owner, ServiceQueue::class, $queue->id); return response()->json(['data' => $this->serializeQueue($queue->fresh(['branch', 'department']))], 201); } public function update(Request $request, ServiceQueue $serviceQueue): JsonResponse { $this->authorizeAbility($request, 'queues.manage'); $this->authorizeOwner($request, $serviceQueue); $validated = $request->validate([ 'name' => ['sometimes', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:1000'], 'prefix' => ['sometimes', 'string', 'max:10'], 'strategy' => ['sometimes', 'string', 'in:'.implode(',', array_keys(config('qms.queue_strategies')))], 'is_active' => ['sometimes', 'boolean'], 'avg_service_seconds' => ['nullable', 'integer', 'min:60', 'max:7200'], 'max_capacity' => ['nullable', 'integer', 'min:1'], ]); $serviceQueue->update($validated); AuditLogger::record( $this->ownerRef($request), 'service_queue.updated', $serviceQueue->organization_id, $this->ownerRef($request), ServiceQueue::class, $serviceQueue->id, ); return response()->json(['data' => $this->serializeQueue($serviceQueue->fresh(['branch', 'department']))]); } public function waiting(Request $request, ServiceQueue $serviceQueue): JsonResponse { $this->authorizeAbility($request, 'queues.view'); $this->authorizeOwner($request, $serviceQueue); $tickets = app(\App\Services\Qms\QueueEngine::class)->waitingTickets($serviceQueue); return response()->json([ 'data' => array_map(fn ($t) => \App\Services\Qms\TicketPresenter::toArray($t), $tickets), ]); } public function pause(Request $request, ServiceQueue $serviceQueue): JsonResponse { $this->authorizeAbility($request, 'queues.manage'); $this->authorizeOwner($request, $serviceQueue); $queue = app(\App\Services\Qms\QueueEngine::class)->pauseQueue($serviceQueue, $this->ownerRef($request)); return response()->json(['data' => ['uuid' => $queue->uuid, 'is_paused' => $queue->is_paused]]); } public function resume(Request $request, ServiceQueue $serviceQueue): JsonResponse { $this->authorizeAbility($request, 'queues.manage'); $this->authorizeOwner($request, $serviceQueue); $queue = app(\App\Services\Qms\QueueEngine::class)->resumeQueue($serviceQueue, $this->ownerRef($request)); return response()->json(['data' => ['uuid' => $queue->uuid, 'is_paused' => $queue->is_paused]]); } /** * @param array $validated */ protected function resolveBranch(int $organizationId, string $owner, array $validated): ?Branch { if (! empty($validated['branch_id'])) { return Branch::owned($owner) ->where('organization_id', $organizationId) ->where('id', (int) $validated['branch_id']) ->first(); } $branchName = trim((string) ($validated['branch_name'] ?? '')); if ($branchName === '') { return null; } return Branch::owned($owner) ->where('organization_id', $organizationId) ->whereRaw('LOWER(name) = ?', [strtolower($branchName)]) ->first(); } /** * @return array */ protected function serializeQueue(ServiceQueue $q): array { return [ 'uuid' => $q->uuid, 'name' => $q->name, 'prefix' => $q->prefix, 'strategy' => $q->strategy, 'is_active' => $q->is_active, 'is_paused' => $q->is_paused, 'branch' => $q->branch?->name, 'external_key' => data_get($q->settings, 'external_key'), ]; } }