diff --git a/.env.example b/.env.example index 84d32f1..897ed6f 100644 --- a/.env.example +++ b/.env.example @@ -26,13 +26,6 @@ SESSION_LIFETIME=1440 CACHE_STORE=redis QUEUE_CONNECTION=database -# Care Queue Engine: native (in-app, default) | remote (legacy Ladill Queue HTTP) -CARE_QUEUE_DRIVER=native - -# Legacy Ladill Queue API (only when CARE_QUEUE_DRIVER=remote) -QUEUE_API_URL=https://queue.ladill.com/api/v1 -QUEUE_API_KEY_CARE= - # --- Ladill SSO (Sign in with Ladill — auth.ladill.com OIDC client) --- LADILL_SSO_CLIENT_ID= LADILL_SSO_CLIENT_SECRET= diff --git a/app/Console/Commands/SyncCareQueueTicketsCommand.php b/app/Console/Commands/SyncCareQueueTicketsCommand.php index be74629..b044b1c 100644 --- a/app/Console/Commands/SyncCareQueueTicketsCommand.php +++ b/app/Console/Commands/SyncCareQueueTicketsCommand.php @@ -9,18 +9,18 @@ use App\Services\Care\CareQueueContexts; use Illuminate\Console\Command; /** - * Backfill Ladill Queue tickets for Care waiters that never received one - * (e.g. demo seed or patients checked in before Queue integration was enabled). + * Backfill Care Queue Engine tickets for waiters that never received one + * (e.g. demo seed or patients checked in before service queues were enabled). */ class SyncCareQueueTicketsCommand extends Command { protected $signature = 'care:queue-sync-tickets - {--organization= : Organization id (default: all with Queue integration on)} + {--organization= : Organization id (default: all with service queues on)} {--branch= : Limit to one Care branch id} {--context=consultation : consultation|pharmacy|laboratory|billing|all} {--limit=100 : Max tickets to issue per branch+context}'; - protected $description = 'Issue missing Queue tickets for Care waiting lists'; + protected $description = 'Issue missing Care Queue Engine tickets for waiting lists'; public function handle(CareQueueBridge $bridge): int { diff --git a/app/Http/Controllers/Care/ServiceQueueController.php b/app/Http/Controllers/Care/ServiceQueueController.php index eb357dd..9694402 100644 --- a/app/Http/Controllers/Care/ServiceQueueController.php +++ b/app/Http/Controllers/Care/ServiceQueueController.php @@ -4,13 +4,12 @@ namespace App\Http\Controllers\Care; use App\Http\Controllers\Controller; use App\Http\Controllers\Care\Concerns\ScopesToAccount; -use App\Services\Care\ServiceQueuePresenter; -use App\Services\Queue\QueueClient; -use Illuminate\Http\Client\RequestException; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; +/** + * Legacy Ladill Queue console routes — redirect to the Care patient queue. + */ class ServiceQueueController extends Controller { use ScopesToAccount; @@ -19,76 +18,20 @@ class ServiceQueueController extends Controller { $this->authorizeAbility($request, 'service_queues.console'); - // Natural Queue: counters live on role pages (clinical queue, pharmacy, lab, specialties). return redirect()->route('care.queue.index'); } - public function console(Request $request, string $counterUuid, QueueClient $queue): View|RedirectResponse + public function console(Request $request, string $counterUuid): RedirectResponse { $this->authorizeAbility($request, 'service_queues.console'); - $organization = $this->organization($request); - $owner = (string) $organization->owner_ref; - if (! $this->integrationEnabled($organization)) { - return redirect()->route('care.settings'); - } - - try { - $state = $queue->console($owner, $counterUuid); - } catch (RequestException) { - return redirect()->route('care.queue.index')->with('error', 'Counter console unavailable.'); - } - - return view('care.service-queues.console', [ - 'state' => $state, - 'counterUuid' => $counterUuid, - 'organization' => $organization, - ]); + return redirect()->route('care.queue.index'); } - public function action(Request $request, string $counterUuid, QueueClient $queue): RedirectResponse + public function action(Request $request, string $counterUuid): RedirectResponse { $this->authorizeAbility($request, 'service_queues.console'); - $organization = $this->organization($request); - $owner = (string) $organization->owner_ref; - $validated = $request->validate([ - 'action' => ['required', 'string'], - 'queue_uuid' => ['nullable', 'uuid'], - 'ticket_uuid' => ['nullable', 'uuid'], - 'to_queue_uuid' => ['nullable', 'uuid'], - 'reason' => ['nullable', 'string', 'max:500'], - ]); - - try { - $result = $queue->consoleAction($owner, $counterUuid, $validated); - } catch (RequestException $e) { - $message = $e->response?->json('message') - ?: $e->response?->json('error') - ?: 'Queue action failed. Try again.'; - - return back()->with('error', is_string($message) ? $message : 'Queue action failed. Try again.'); - } - - $success = match ($validated['action']) { - 'transfer' => sprintf( - 'Handed off %s to %s. Ticket number unchanged.', - (string) ($result['ticket_number'] ?? 'ticket'), - (string) data_get($result, 'queue.name', 'next queue'), - ), - 'call_next' => ! empty($result['ticket_number']) - ? "Called {$result['ticket_number']}." - : 'No tickets waiting in that queue.', - 'available' => 'Counter marked available.', - 'offline' => 'Counter went offline.', - default => 'Queue updated.', - }; - - return back()->with('success', $success); - } - - protected function integrationEnabled(\App\Models\Organization $organization): bool - { - return (bool) data_get($organization->settings, 'queue_integration_enabled', false); + return redirect()->route('care.queue.index'); } } diff --git a/app/Http/Controllers/Care/SettingsController.php b/app/Http/Controllers/Care/SettingsController.php index 023c94d..1810d5c 100644 --- a/app/Http/Controllers/Care/SettingsController.php +++ b/app/Http/Controllers/Care/SettingsController.php @@ -19,7 +19,6 @@ use App\Services\Care\PlanService; use App\Services\Care\Workflow\WorkflowTemplateInstaller; use App\Services\Care\Workflow\WorkflowTemplateRegistry; use App\Services\Messaging\MessagingCredentialsService; -use App\Services\Queue\QueueClient; use App\Support\OrganizationBranding; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -79,7 +78,7 @@ class SettingsController extends Controller ]); } - public function update(Request $request, QueueClient $queue): RedirectResponse + public function update(Request $request): RedirectResponse { $this->authorizeAbility($request, 'settings.manage'); $organization = $this->organization($request); @@ -130,7 +129,7 @@ class SettingsController extends Controller if ($wantsQueue && ! $plans->canUseQueueIntegration($organization)) { return redirect() ->route('care.pro.index') - ->with('upsell', 'Ladill Queue integration is part of Care Pro. Upgrade to connect service queues and counters.'); + ->with('upsell', 'In-app service queues are part of Care Pro. Upgrade to enable ticket numbers and Call next.'); } if (! $plans->canUseQueueIntegration($organization)) { $wantsQueue = false; @@ -144,15 +143,6 @@ class SettingsController extends Controller $settings[AppointmentPatientNotifier::SETTING_VIDEO_SMS] = $request->boolean('notify_video_scheduled_sms'); $settings[AppointmentPatientNotifier::SETTING_VIDEO_EMAIL] = $request->boolean('notify_video_scheduled_email'); - if ($wantsQueue && config('care.queue.driver', 'native') !== 'native' && $queue->configured()) { - $branch = Branch::owned($owner)->where('organization_id', $organization->id)->orderBy('name')->first(); - try { - $queue->enable($owner, $organization->name, $branch?->name); - } catch (\Throwable) { - return back()->withInput()->with('error', 'Could not provision Ladill Queue for this account. Ensure Queue API keys are configured and Queue allows Care integration.'); - } - } - $logoPath = $organization->logo_path; if ($request->boolean('remove_logo')) { @@ -187,8 +177,7 @@ class SettingsController extends Controller } // Queue department sync + waiting-ticket backfill — run after the redirect. - $queueReady = config('care.queue.driver', 'native') === 'native' || $queue->configured(); - if ($wantsQueue && $queueReady) { + if ($wantsQueue) { $organizationId = (int) $organization->id; $backfillTickets = $queueJustEnabled; dispatch(function () use ($organizationId, $owner, $backfillTickets) { diff --git a/app/Http/Middleware/EnsurePaidPlan.php b/app/Http/Middleware/EnsurePaidPlan.php index a93242f..f43594e 100644 --- a/app/Http/Middleware/EnsurePaidPlan.php +++ b/app/Http/Middleware/EnsurePaidPlan.php @@ -9,7 +9,7 @@ use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; /** - * Blocks free-plan accounts from Pro modules (lab, pharmacy, billing, Queue integration). + * Blocks free-plan accounts from Pro modules (lab, pharmacy, billing, service queues). */ class EnsurePaidPlan { @@ -39,6 +39,6 @@ class EnsurePaidPlan return redirect() ->route('care.pro.index') - ->with('upsell', 'Lab, pharmacy, billing, and Queue integration are part of Care Pro. Upgrade to unlock them.'); + ->with('upsell', 'Lab, pharmacy, billing, and in-app service queues are part of Care Pro. Upgrade to unlock them.'); } } diff --git a/app/Services/Care/CareQueueBridge.php b/app/Services/Care/CareQueueBridge.php index d266d40..255665e 100644 --- a/app/Services/Care/CareQueueBridge.php +++ b/app/Services/Care/CareQueueBridge.php @@ -11,19 +11,15 @@ use App\Models\Patient; use App\Models\Practitioner; use App\Models\Prescription; use App\Services\Care\Workflow\WorkflowQueueGate; -use App\Services\Queue\QueueClient; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\Client\RequestException; use Illuminate\Support\Facades\Log; /** - * Bridges Care workflow entities to service-queue tickets (native Care Queue - * Engine by default; optional legacy Ladill Queue HTTP when driver=remote). + * Bridges Care workflow entities to Care Queue Engine tickets. */ class CareQueueBridge { public function __construct( - protected QueueClient $queue, protected CareQueueEngine $engine, protected CareQueueProvisioner $provisioner, protected PlanService $plans, @@ -31,21 +27,10 @@ class CareQueueBridge protected WorkflowQueueGate $workflowGate, ) {} - public function usesNative(): bool - { - return config('care.queue.driver', 'native') === 'native'; - } - public function isEnabled(Organization $organization): bool { - $enabled = $this->plans->canUseQueueIntegration($organization) + return $this->plans->canUseQueueIntegration($organization) && (bool) data_get($organization->settings, 'queue_integration_enabled'); - - if (! $enabled) { - return false; - } - - return $this->usesNative() || $this->queue->configured(); } public function contextForAppointment(Organization $organization, Appointment $appointment): string @@ -380,25 +365,11 @@ class CareQueueBridge protected function attemptCallNext(Organization $organization, array $resources): ?array { try { - if ($this->usesNative()) { - return $this->engine->callNext( - $organization, - (string) $resources['queue_uuid'], - (string) $resources['counter_uuid'], - ); - } - - return $this->queue->callNext( - (string) $organization->owner_ref, + return $this->engine->callNext( + $organization, (string) $resources['queue_uuid'], (string) $resources['counter_uuid'], ); - } catch (RequestException $e) { - Log::warning('care.queue_call_next_failed', [ - 'message' => $e->getMessage(), - ]); - - return null; } catch (\Throwable $e) { Log::warning('care.queue_call_next_failed', [ 'message' => $e->getMessage(), @@ -583,7 +554,6 @@ class CareQueueBridge return null; } - $owner = (string) $organization->owner_ref; $payload = [ 'service_queue_id' => $resources['queue_uuid'], 'customer_name' => $patient?->fullName(), @@ -597,11 +567,7 @@ class CareQueueBridge } try { - if ($this->usesNative()) { - return $this->engine->issueTicket($organization, $payload); - } - - return $this->queue->issueTicket($owner, $payload); + return $this->engine->issueTicket($organization, $payload); } catch (\Throwable $e) { Log::warning('care.queue_issue_failed', [ 'context' => $context, @@ -650,15 +616,9 @@ class CareQueueBridge } try { - if ($this->usesNative()) { - $ticket = $this->engine->ticketAction($organization, $uuid, [ - 'action' => $action, - ]); - } else { - $ticket = $this->queue->ticketAction((string) $organization->owner_ref, $uuid, [ - 'action' => $action, - ]); - } + $ticket = $this->engine->ticketAction($organization, $uuid, [ + 'action' => $action, + ]); $this->applyTicket($entity, $ticket); return $ticket; diff --git a/app/Services/Care/CareQueueProvisioner.php b/app/Services/Care/CareQueueProvisioner.php index 127a050..2eccb5a 100644 --- a/app/Services/Care/CareQueueProvisioner.php +++ b/app/Services/Care/CareQueueProvisioner.php @@ -8,36 +8,22 @@ use App\Models\CareServiceQueue; use App\Models\Member; use App\Models\Organization; use App\Models\Practitioner; -use App\Services\Queue\QueueClient; use Illuminate\Support\Facades\Log; /** - * Idempotently provisions service queues and service points from Care staff / - * branch configuration. Native driver writes Care Queue Engine tables; remote - * driver syncs to Ladill Queue over HTTP (legacy cutover). + * Idempotently provisions Care Queue Engine service queues and service points + * from Care staff / branch configuration. */ class CareQueueProvisioner { public function __construct( - protected QueueClient $queue, protected PlanService $plans, ) {} - public function usesNative(): bool - { - return config('care.queue.driver', 'native') === 'native'; - } - public function shouldProvision(Organization $organization): bool { - $enabled = $this->plans->canUseQueueIntegration($organization) + return $this->plans->canUseQueueIntegration($organization) && (bool) data_get($organization->settings, 'queue_integration_enabled'); - - if (! $enabled) { - return false; - } - - return $this->usesNative() || $this->queue->configured(); } /** @@ -121,95 +107,6 @@ class CareQueueProvisioner 'synced' => false, ]; - if ($this->usesNative()) { - return $this->provisionBranchDepartmentNative($organization, $owner, $context, $meta, $branch, $stub); - } - - try { - $this->ensureBranchName($owner, $branch->name); - - $department = $this->queue->ensureDepartment($owner, [ - 'name' => $meta['name'], - 'type' => $meta['type'] ?? 'general', - 'branch_name' => $branch->name, - 'external_key' => $stub['department_external_key'], - 'is_active' => true, - ]); - $stub['department_id'] = $department['id'] ?? $stub['department_id']; - - $queue = $this->queue->ensureQueue($owner, [ - 'name' => $meta['name'], - 'prefix' => $meta['prefix'], - 'branch_name' => $branch->name, - 'strategy' => 'fifo', - 'external_key' => $stub['queue_external_key'], - 'routing_mode' => $meta['routing_mode'], - 'department_id' => $stub['department_id'], - 'is_active' => true, - ]); - $stub['queue_uuid'] = $queue['uuid'] ?? $stub['queue_uuid']; - - $points = $this->buildPointsForContext($organization, $context, $branch, $stub); - $syncedPoints = []; - foreach ($points as $point) { - try { - $counter = $this->queue->ensureCounter($owner, [ - 'name' => $point['name'], - 'destination' => $point['destination'], - 'staff_ref' => $point['staff_ref'], - 'staff_display_name' => $point['staff_display_name'], - 'branch_name' => $branch->name, - 'department_id' => $stub['department_id'], - 'queue_uuids' => array_values(array_filter([(string) $stub['queue_uuid']])), - 'external_key' => $point['external_key'], - 'is_active' => true, - ]); - $point['counter_uuid'] = $counter['uuid'] ?? null; - $point['synced'] = ! empty($point['counter_uuid']); - } catch (\Throwable $e) { - Log::warning('care.queue_point_provision_failed', [ - 'context' => $context, - 'branch_id' => $branch->id, - 'point' => $point['external_key'] ?? null, - 'message' => $e->getMessage(), - ]); - $point['synced'] = false; - } - $syncedPoints[] = $point; - } - - $stub['points'] = $syncedPoints; - // Legacy single counter: first point (or dedicated default) for older callers. - $first = collect($syncedPoints)->first(fn ($p) => ! empty($p['counter_uuid'])); - if ($first) { - $stub['counter_uuid'] = $first['counter_uuid']; - $stub['counter_external_key'] = $first['external_key']; - } - $stub['synced'] = ! empty($stub['queue_uuid']) && collect($syncedPoints)->contains(fn ($p) => ! empty($p['synced'])); - } catch (\Throwable $e) { - Log::warning('care.queue_department_provision_failed', [ - 'context' => $context, - 'branch_id' => $branch->id, - 'message' => $e->getMessage(), - ]); - } - - return $stub; - } - - /** - * @param array $meta - * @param array $stub - * @return array - */ - protected function provisionBranchDepartmentNative( - Organization $organization, - string $owner, - string $context, - array $meta, - Branch $branch, - array $stub, - ): array { try { $queue = CareServiceQueue::query()->updateOrCreate( [ @@ -264,7 +161,6 @@ class CareQueueProvisioner $syncedPoints[] = $point; } - // Soft-deactivate points no longer in the staff roster. CareServicePoint::query() ->where('service_queue_id', $queue->id) ->when( @@ -302,7 +198,6 @@ class CareQueueProvisioner Branch $branch, array $stub, ): array { - $owner = (string) $organization->owner_ref; $priorByKey = collect($stub['points'] ?? [])->keyBy(fn ($p) => (string) ($p['external_key'] ?? '')); $points = match (true) { @@ -362,7 +257,6 @@ class CareQueueProvisioner default => [], }; - // Always keep at least one default point so call-next / least-load have a target. if ($points === []) { $key = CareQueueContexts::pointExternalKey($context, $branch->id, 'default', 0); $prior = $priorByKey->get($key, []); @@ -487,17 +381,6 @@ class CareQueueProvisioner return $points; } - protected function ensureBranchName(string $owner, string $branchName): void - { - try { - $this->queue->ensureBranch($owner, ['name' => $branchName]); - } catch (\Illuminate\Http\Client\RequestException $e) { - if ($e->response?->status() !== 422) { - throw $e; - } - } - } - /** * @return array{queue_uuid: ?string, counter_uuid: ?string, queue_external_key: string, counter_external_key: string, synced: bool, points: list>, routing_mode: string}|null */ @@ -632,8 +515,6 @@ class CareQueueProvisioner if ($existing && ! empty($existing['queue_uuid']) && ( ! empty($existing['points']) || ! empty($existing['counter_uuid']) ) && $modeMatches) { - // Specialty module stubs often only store a legacy counter — still provision - // practitioner points so check-in / sync can assign real desks. if (! CareQueueContexts::isSpecialty($context) || ! empty($existing['points'])) { return $existing; } diff --git a/app/Services/Care/DemoTenantSeeder.php b/app/Services/Care/DemoTenantSeeder.php index 3216acb..67fdd7b 100644 --- a/app/Services/Care/DemoTenantSeeder.php +++ b/app/Services/Care/DemoTenantSeeder.php @@ -734,16 +734,8 @@ class DemoTenantSeeder private function seedSpecialtyModules(Organization $organization, string $ownerRef): void { - // Remote Queue HTTP during per-module activate can hang Enterprise demos. - // Native Care Queue Engine is local — keep integration on so desks provision. $organization = $organization->fresh(); - $settings = $organization->settings ?? []; - $queueEnabled = (bool) data_get($settings, 'queue_integration_enabled'); - $native = config('care.queue.driver', 'native') === 'native'; - if ($queueEnabled && ! $native) { - data_set($settings, 'queue_integration_enabled', false); - $organization->update(['settings' => $settings]); - } + $queueEnabled = (bool) data_get($organization->settings, 'queue_integration_enabled'); $service = app(SpecialtyModuleService::class); foreach (array_keys($service->catalog()) as $key) { @@ -754,13 +746,7 @@ class DemoTenantSeeder } } - if ($queueEnabled && ! $native) { - $organization = $organization->fresh(); - $settings = $organization->settings ?? []; - data_set($settings, 'queue_integration_enabled', true); - $organization->update(['settings' => $settings]); - // Skip CareQueueProvisioner here — same Queue HTTP storm as ticket sync. - } elseif ($queueEnabled && $native) { + if ($queueEnabled) { try { app(CareQueueProvisioner::class)->provisionOrganization($organization->fresh()); } catch (\Throwable) { diff --git a/app/Services/Care/PlanService.php b/app/Services/Care/PlanService.php index bb89c4f..f346715 100644 --- a/app/Services/Care/PlanService.php +++ b/app/Services/Care/PlanService.php @@ -42,7 +42,7 @@ class PlanService return in_array($this->planKey($organization), ['pro', 'enterprise'], true); } - /** Lab, pharmacy, billing, and Ladill Queue integration require Pro or Enterprise. */ + /** Lab, pharmacy, billing, and in-app service queues require Pro or Enterprise. */ public function canUseProModules(Organization $organization): bool { return $this->hasPaidPlan($organization); diff --git a/app/Services/Care/ServiceQueuePresenter.php b/app/Services/Care/ServiceQueuePresenter.php deleted file mode 100644 index 4318640..0000000 --- a/app/Services/Care/ServiceQueuePresenter.php +++ /dev/null @@ -1,327 +0,0 @@ -> */ - protected array $contextKeywords = [ - 'clinical' => ['reception', 'consult', 'outpatient', 'general', 'doctor', 'nurse', 'triage', 'waiting'], - 'pharmacy' => ['pharm', 'dispens', 'drug', 'rx'], - 'laboratory' => ['lab', 'sample', 'patholog', 'investig'], - 'dentistry' => ['dent', 'dental', 'oral'], - 'ophthalmology' => ['eye', 'ophthal', 'optom'], - 'physiotherapy' => ['physio', 'rehab', 'therapy'], - 'maternity' => ['matern', 'antenatal', 'obstetric'], - 'radiology' => ['radio', 'imaging', 'x-ray', 'xray', 'ultrasound', 'ct', 'mri'], - ]; - - public function __construct( - protected QueueClient $queue, - protected CarePermissions $permissions, - protected PlanService $plans, - protected OrganizationResolver $resolver, - ) {} - - /** - * @return array{ - * enabled: bool, - * counters: list>, - * counter_uuid: ?string, - * state: ?array, - * stubs: list>, - * error: ?string, - * context: string - * }|null - */ - public function panel( - Request $request, - Organization $organization, - ?Member $member, - string $context = 'clinical', - ?string $preferredCounterUuid = null, - ): ?array { - if (! $this->shouldShow($organization, $member)) { - return null; - } - - $owner = (string) $organization->owner_ref; - $branchScope = $this->resolver->branchScope($member); - $branchName = null; - if ($branchScope) { - $branchName = Branch::owned($owner)->where('id', $branchScope)->value('name'); - } - - $stubs = $this->stubsForContext($organization, $context, $branchScope); - - if (config('care.queue.driver', 'native') === 'native') { - $counters = $this->nativeCountersFromStubs($stubs, $context); - $stubCounterUuid = collect($stubs) - ->map(fn ($s) => (string) ($s['counter_uuid'] ?? '')) - ->first(fn ($uuid) => $uuid !== ''); - $counterUuid = $preferredCounterUuid - ?: ($stubCounterUuid ?: '') - ?: (string) ($counters[0]['uuid'] ?? ''); - - return [ - 'enabled' => true, - 'counters' => $counters, - 'counter_uuid' => $counterUuid !== '' ? $counterUuid : null, - 'state' => null, - 'stubs' => $stubs, - 'error' => null, - 'context' => $context, - ]; - } - - if (! $this->queue->configured()) { - return [ - 'enabled' => true, - 'counters' => [], - 'counter_uuid' => null, - 'state' => null, - 'stubs' => $stubs, - 'error' => 'Ladill Queue API is not configured on this Care instance.', - 'context' => $context, - ]; - } - - try { - $counters = $this->queue->counters($owner); - } catch (RequestException $e) { - if ($e->response?->status() === 403) { - try { - $this->queue->syncIntegration($owner, true); - $counters = $this->queue->counters($owner); - } catch (RequestException) { - return [ - 'enabled' => true, - 'counters' => [], - 'counter_uuid' => null, - 'state' => null, - 'stubs' => $stubs, - 'error' => 'Queue integration is not linked. Re-save Settings with Queue enabled.', - 'context' => $context, - ]; - } - } else { - return [ - 'enabled' => true, - 'counters' => [], - 'counter_uuid' => null, - 'state' => null, - 'stubs' => $stubs, - 'error' => 'Could not reach Ladill Queue.', - 'context' => $context, - ]; - } - } - - $stubCounterUuid = collect($stubs) - ->map(fn ($s) => (string) ($s['counter_uuid'] ?? '')) - ->first(fn ($uuid) => $uuid !== ''); - - $counters = $this->filterCounters($counters, $branchName, $context, $stubCounterUuid); - $counterUuid = $preferredCounterUuid - ?: ($stubCounterUuid ?: '') - ?: (string) ($counters[0]['uuid'] ?? ''); - - if ($counterUuid !== '' && ! collect($counters)->contains(fn ($c) => ($c['uuid'] ?? '') === $counterUuid)) { - $counterUuid = (string) ($counters[0]['uuid'] ?? ''); - } - - $state = null; - if ($counterUuid !== '') { - try { - $state = $this->queue->console($owner, $counterUuid); - } catch (RequestException) { - return [ - 'enabled' => true, - 'counters' => $counters, - 'counter_uuid' => $counterUuid, - 'state' => null, - 'stubs' => $stubs, - 'error' => 'Counter console unavailable.', - 'context' => $context, - ]; - } - } - - return [ - 'enabled' => true, - 'counters' => $counters, - 'counter_uuid' => $counterUuid !== '' ? $counterUuid : null, - 'state' => $state, - 'stubs' => $stubs, - 'error' => null, - 'context' => $context, - ]; - } - - public function shouldShow(Organization $organization, ?Member $member): bool - { - if (! $member) { - return false; - } - - if (! $this->plans->canUseQueueIntegration($organization)) { - return false; - } - - if (! data_get($organization->settings, 'queue_integration_enabled')) { - return false; - } - - return $this->permissions->can($member, 'service_queues.console'); - } - - /** - * @param list> $counters - * @return list> - */ - protected function filterCounters( - array $counters, - ?string $branchName, - string $context, - ?string $preferredUuid = null, - ): array { - $all = collect($counters); - $filtered = $all; - - if ($preferredUuid) { - $linked = $all->first(fn ($c) => ($c['uuid'] ?? '') === $preferredUuid); - if ($linked) { - return [$linked]; - } - } - - if ($branchName) { - $branchFiltered = $filtered->filter(function ($counter) use ($branchName) { - $counterBranch = (string) ($counter['branch'] ?? ''); - - return $counterBranch === '' || strcasecmp($counterBranch, $branchName) === 0; - }); - if ($branchFiltered->isNotEmpty()) { - $filtered = $branchFiltered; - } - } - - $keywords = $this->keywordsFor($context); - if ($keywords !== []) { - $keywordFiltered = $filtered->filter(function ($counter) use ($keywords) { - $queueNames = collect($counter['queues'] ?? [])->map(function ($q) { - return is_array($q) ? (string) ($q['name'] ?? '') : (string) $q; - })->implode(' '); - - $haystack = strtolower(implode(' ', [ - (string) ($counter['name'] ?? ''), - $queueNames, - ])); - - foreach ($keywords as $keyword) { - if ($keyword !== '' && str_contains($haystack, $keyword)) { - return true; - } - } - - return false; - }); - - // Fall back to branch-scoped counters when no keyword match (demo / generic counters). - if ($keywordFiltered->isNotEmpty()) { - $filtered = $keywordFiltered; - } - } - - return $filtered->values()->all(); - } - - /** - * @param list> $stubs - * @return list> - */ - protected function nativeCountersFromStubs(array $stubs, string $context): array - { - $counters = []; - foreach ($stubs as $stub) { - $branchName = $stub['branch_name'] ?? null; - $queueName = $stub['name'] ?? $context; - $points = is_array($stub['points'] ?? null) ? $stub['points'] : []; - - foreach ($points as $point) { - $uuid = (string) ($point['counter_uuid'] ?? ''); - if ($uuid === '') { - continue; - } - $counters[] = [ - 'uuid' => $uuid, - 'name' => (string) ($point['name'] ?? $point['destination'] ?? 'Desk'), - 'branch' => $branchName, - 'queues' => [['name' => $queueName]], - ]; - } - - if ($points === [] && ! empty($stub['counter_uuid'])) { - $counters[] = [ - 'uuid' => (string) $stub['counter_uuid'], - 'name' => (string) $queueName, - 'branch' => $branchName, - 'queues' => [['name' => $queueName]], - ]; - } - } - - return $counters; - } - - /** - * @return list - */ - protected function keywordsFor(string $context): array - { - if (isset($this->contextKeywords[$context])) { - return $this->contextKeywords[$context]; - } - - $module = config("care.specialty_modules.{$context}.queue_keywords"); - - return is_array($module) ? $module : []; - } - - /** - * @return list> - */ - protected function stubsForContext(Organization $organization, string $context, ?int $branchId): array - { - if (! array_key_exists($context, config('care.specialty_modules', []))) { - return []; - } - - $queues = data_get($organization->settings, "specialty_module_provisioning.{$context}.queues", []); - if (! is_array($queues)) { - return []; - } - - return array_values(array_filter($queues, function ($queue) use ($branchId) { - if (! ($queue['active'] ?? false)) { - return false; - } - if ($branchId !== null && (int) ($queue['branch_id'] ?? 0) !== $branchId) { - return false; - } - - return true; - })); - } -} diff --git a/app/Services/Care/SpecialtyModuleService.php b/app/Services/Care/SpecialtyModuleService.php index 6957aeb..9b890a8 100644 --- a/app/Services/Care/SpecialtyModuleService.php +++ b/app/Services/Care/SpecialtyModuleService.php @@ -3,18 +3,18 @@ namespace App\Services\Care; use App\Models\Branch; +use App\Models\CareServiceQueue; use App\Models\Department; use App\Models\Member; use App\Models\Organization; use App\Models\Practitioner; -use App\Services\Queue\QueueClient; use Illuminate\Support\Facades\Log; /** * Activate/deactivate specialty practice modules (dentistry, eye care, …). * * Persistence: organization.settings.specialty_modules[key] = bool - * Provisioning: departments per branch + Care-side queue stubs (and best-effort Queue API sync). + * Provisioning: departments per branch + Care Queue Engine stubs/points. * Deactivate hides UI and marks queues inactive; does not destroy clinical history. * * Plan gate: Pro/Enterprise via PlanService feature specialty_modules (same tier as queue_integration). @@ -23,7 +23,6 @@ class SpecialtyModuleService { public function __construct( protected PlanService $plans, - protected QueueClient $queue, ) {} /** @@ -332,38 +331,18 @@ class SpecialtyModuleService $fresh = $organization->fresh(); if (data_get($fresh?->settings, 'queue_integration_enabled')) { - if (config('care.queue.driver', 'native') === 'native') { - $provisioner = app(CareQueueProvisioner::class); - $branches = Branch::owned($ownerRef) - ->where('organization_id', $organization->id) - ->where('is_active', true) - ->pluck('id'); - foreach ($branches as $branchId) { - try { - $provisioner->ensure($fresh, $key, (int) $branchId); - } catch (\Throwable $e) { - Log::warning('specialty_module.native_queue_provision_failed', [ - 'key' => $key, - 'branch_id' => $branchId, - 'message' => $e->getMessage(), - ]); - } - } - } elseif ($this->queue->configured()) { + $provisioner = app(CareQueueProvisioner::class); + $branches = Branch::owned($ownerRef) + ->where('organization_id', $organization->id) + ->where('is_active', true) + ->pluck('id'); + foreach ($branches as $branchId) { try { - $synced = $this->queue->syncSpecialtyQueueStubs($ownerRef, $queueStubs); - $settings = $fresh->settings ?? []; - $provisioning = is_array($settings['specialty_module_provisioning'] ?? null) - ? $settings['specialty_module_provisioning'] - : []; - $provisioning[$key] = array_merge($provisioning[$key] ?? [], [ - 'queues' => $synced, - ]); - $settings['specialty_module_provisioning'] = $provisioning; - $organization->update(['settings' => $settings]); + $provisioner->ensure($fresh, $key, (int) $branchId); } catch (\Throwable $e) { - Log::warning('specialty_module.queue_sync_failed', [ + Log::warning('specialty_module.native_queue_provision_failed', [ 'key' => $key, + 'branch_id' => $branchId, 'message' => $e->getMessage(), ]); } @@ -399,27 +378,21 @@ class SpecialtyModuleService } $queues = is_array($record['queues'] ?? null) ? $record['queues'] : []; + foreach ($queues as $i => $queue) { + $queues[$i]['active'] = false; + } - if ( - data_get($settings, 'queue_integration_enabled') - && config('care.queue.driver', 'native') !== 'native' - && $this->queue->configured() - && $queues !== [] - ) { + if (data_get($settings, 'queue_integration_enabled') && $queues !== []) { try { - $queues = $this->queue->deactivateSpecialtyQueueStubs($ownerRef, $queues); + CareServiceQueue::query() + ->where('organization_id', $organization->id) + ->where('context', $key) + ->update(['is_active' => false]); } catch (\Throwable $e) { Log::warning('specialty_module.queue_deactivate_failed', [ 'key' => $key, 'message' => $e->getMessage(), ]); - foreach ($queues as $i => $queue) { - $queues[$i]['active'] = false; - } - } - } else { - foreach ($queues as $i => $queue) { - $queues[$i]['active'] = false; } } diff --git a/app/Services/Queue/QueueClient.php b/app/Services/Queue/QueueClient.php deleted file mode 100644 index b955e4d..0000000 --- a/app/Services/Queue/QueueClient.php +++ /dev/null @@ -1,397 +0,0 @@ -token()) - ->acceptJson() - ->timeout(12) - ->withQueryParameters(['owner' => $owner]); - } - - public function configured(): bool - { - return $this->token() !== '' && $this->base() !== ''; - } - - /** - * @return array - */ - public function status(string $owner): array - { - $response = $this->http($owner)->get($this->base().'/integrations/status'); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @return array - */ - public function enable(string $owner, string $organizationName, ?string $branchName = null): array - { - $this->http($owner)->post($this->base().'/integrations/provision', array_filter([ - 'organization_name' => $organizationName, - 'branch_name' => $branchName, - 'industry' => 'healthcare', - ]))->throw(); - - return $this->syncIntegration($owner, true); - } - - /** - * @return array - */ - public function syncIntegration(string $owner, bool $enabled = true): array - { - $response = $this->http($owner)->patch($this->base().'/integrations', [ - 'care_enabled' => $enabled, - ]); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - public function disable(string $owner): void - { - // Local Care setting only; Queue must be disabled separately in Queue settings. - } - - public function isLinked(string $owner): bool - { - if (! $this->configured()) { - return false; - } - - try { - $status = $this->status($owner); - - return (bool) ($status['provisioned'] ?? false) - && (bool) data_get($status, 'integrations.care', false); - } catch (RequestException) { - return false; - } - } - - /** - * @return list> - */ - public function counters(string $owner): array - { - $response = $this->http($owner)->get($this->base().'/counters'); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @return list> - */ - public function branches(string $owner): array - { - $response = $this->http($owner)->get($this->base().'/branches'); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @param array $payload - * @return array - */ - public function ensureDepartment(string $owner, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/departments', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @param array $payload - * @return array - */ - public function ensureBranch(string $owner, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/branches', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @param array $payload - * @return array - */ - public function ensureQueue(string $owner, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/queues', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @param array $payload - * @return array - */ - public function ensureCounter(string $owner, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/counters', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * Soft-deactivate a Queue counter without destroying history. - * - * @return array - */ - public function updateCounter(string $owner, string $counterUuid, array $payload): array - { - $response = $this->http($owner)->patch($this->base().'/counters/'.$counterUuid, $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * Soft-deactivate a Queue service queue without destroying history. - * - * @return array - */ - public function updateQueue(string $owner, string $queueUuid, array $payload): array - { - $response = $this->http($owner)->patch($this->base().'/queues/'.$queueUuid, $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @return array - */ - public function console(string $owner, string $counterUuid): array - { - $response = $this->http($owner)->get($this->base().'/counters/'.$counterUuid.'/console'); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @param array $payload - * @return array - */ - public function consoleAction(string $owner, string $counterUuid, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/counters/'.$counterUuid.'/console', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @param array $payload - * @return array - */ - public function issueTicket(string $owner, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/tickets', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * @return array|null - */ - public function callNext(string $owner, string $queueUuid, string $counterUuid): ?array - { - $response = $this->http($owner)->post($this->base().'/queues/'.$queueUuid.'/call-next', [ - 'counter_id' => $counterUuid, - ]); - $response->throw(); - - $data = $response->json('data'); - - return is_array($data) ? $data : null; - } - - /** - * @param array $payload - * @return array - */ - public function ticketAction(string $owner, string $ticketUuid, array $payload): array - { - $response = $this->http($owner)->post($this->base().'/tickets/'.$ticketUuid.'/action', $payload); - $response->throw(); - - return (array) ($response->json('data') ?? []); - } - - /** - * Create or link real Queue queues/counters for Care specialty stubs. - * Idempotent via external_key (and Queue-side name+branch fallback). - * - * @param list> $stubs - * @return list> - */ - public function syncSpecialtyQueueStubs(string $owner, array $stubs): array - { - if (! $this->configured() || $stubs === []) { - return $stubs; - } - - try { - if (! $this->isLinked($owner)) { - return $stubs; - } - } catch (\Throwable) { - return $stubs; - } - - foreach ($stubs as $i => $stub) { - if (! ($stub['active'] ?? true)) { - continue; - } - - try { - $stubs[$i] = $this->ensureSpecialtyStub($owner, $stub); - } catch (\Throwable $e) { - Log::warning('queue.specialty_stub_sync_failed', [ - 'owner' => $owner, - 'stub' => $stub['name'] ?? null, - 'branch' => $stub['branch_name'] ?? null, - 'message' => $e->getMessage(), - ]); - $stubs[$i]['synced'] = false; - } - } - - return $stubs; - } - - /** - * Soft-deactivate Queue resources linked on Care stubs (leaves history intact). - * - * @param list> $stubs - * @return list> - */ - public function deactivateSpecialtyQueueStubs(string $owner, array $stubs): array - { - if (! $this->configured() || $stubs === []) { - return $stubs; - } - - foreach ($stubs as $i => $stub) { - try { - if (! empty($stub['counter_uuid'])) { - $this->updateCounter($owner, (string) $stub['counter_uuid'], ['is_active' => false]); - } - if (! empty($stub['queue_uuid'])) { - $this->updateQueue($owner, (string) $stub['queue_uuid'], ['is_active' => false]); - } - } catch (\Throwable $e) { - Log::warning('queue.specialty_stub_deactivate_failed', [ - 'owner' => $owner, - 'counter_uuid' => $stub['counter_uuid'] ?? null, - 'queue_uuid' => $stub['queue_uuid'] ?? null, - 'message' => $e->getMessage(), - ]); - } - - $stubs[$i]['active'] = false; - } - - return $stubs; - } - - /** - * @param array $stub - * @return array - */ - protected function ensureSpecialtyStub(string $owner, array $stub): array - { - $branchName = trim((string) ($stub['branch_name'] ?? '')); - $name = trim((string) ($stub['name'] ?? '')); - $prefix = trim((string) ($stub['prefix'] ?? 'A')); - $module = (string) ($stub['module'] ?? 'specialty'); - $careBranchId = (string) ($stub['branch_id'] ?? '0'); - - if ($branchName === '' || $name === '') { - $stub['synced'] = false; - - return $stub; - } - - $queueKey = (string) ($stub['queue_external_key'] ?? "care:specialty:{$module}:queue:{$careBranchId}"); - $counterKey = (string) ($stub['counter_external_key'] ?? "care:specialty:{$module}:counter:{$careBranchId}"); - - try { - $this->ensureBranch($owner, ['name' => $branchName]); - } catch (RequestException $e) { - // Free Queue plans may already be at branch cap — continue if a branch can be resolved. - if ($e->response?->status() !== 422) { - throw $e; - } - $branches = collect($this->branches($owner)); - $existing = $branches->first( - fn ($b) => strcasecmp((string) ($b['name'] ?? ''), $branchName) === 0 - ); - if (! $existing && $branches->count() === 1) { - // Single-location Queue orgs often use a different default name (e.g. "Main Branch"). - $existing = $branches->first(); - $branchName = (string) ($existing['name'] ?? $branchName); - } - if (! $existing) { - throw $e; - } - } - - $queue = $this->ensureQueue($owner, [ - 'name' => $name, - 'prefix' => $prefix !== '' ? $prefix : 'A', - 'branch_name' => $branchName, - 'strategy' => 'fifo', - 'external_key' => $queueKey, - 'routing_mode' => (string) ($stub['routing_mode'] ?? 'shared_pool'), - 'is_active' => true, - ]); - - $counter = $this->ensureCounter($owner, [ - 'name' => $name.' counter', - 'destination' => $name, - 'branch_name' => $branchName, - 'queue_uuids' => array_values(array_filter([(string) ($queue['uuid'] ?? '')])), - 'external_key' => $counterKey, - 'is_active' => true, - ]); - - $stub['queue_external_key'] = $queueKey; - $stub['counter_external_key'] = $counterKey; - $stub['queue_uuid'] = $queue['uuid'] ?? null; - $stub['counter_uuid'] = $counter['uuid'] ?? null; - $stub['synced'] = ! empty($stub['queue_uuid']) && ! empty($stub['counter_uuid']); - - return $stub; - } -} diff --git a/config/care.php b/config/care.php index 6282ba1..fb257dc 100644 --- a/config/care.php +++ b/config/care.php @@ -396,13 +396,6 @@ return [ 'period_days' => (int) env('CARE_PRO_PERIOD_DAYS', 30), ], - 'queue' => [ - // native = Care Queue Engine (in-app). remote = Ladill Queue HTTP (legacy cutover). - 'driver' => env('CARE_QUEUE_DRIVER', 'native'), - 'api_url' => env('QUEUE_API_URL', 'https://queue.ladill.com/api/v1'), - 'api_key' => env('QUEUE_API_KEY_CARE'), - ], - 'upgrade_banner' => [ 'free' => [ 'title' => 'Unlock Care Pro or Enterprise', diff --git a/docs/care-queue-engine-and-specialty-plan.md b/docs/care-queue-engine-and-specialty-plan.md index ff286a1..39b8e50 100644 --- a/docs/care-queue-engine-and-specialty-plan.md +++ b/docs/care-queue-engine-and-specialty-plan.md @@ -1,6 +1,6 @@ # Care Queue Engine + Specialty Modules — Unified Plan -**Status:** Phase 4 complete (Queue healthcare removed); Phase 5 next +**Status:** Phase 5 complete (Care remote Queue adapter deleted) **Date:** 2026-07-18 **Repos:** ladill-care (primary), ladill-queue (healthcare removal) **Related:** `docs/specialty-module-design-standard.md` @@ -9,7 +9,7 @@ ## Phase 1 notes (implementation) -- Config: `CARE_QUEUE_DRIVER=native` (default) | `remote` (legacy Ladill Queue HTTP) +- Config: Care Queue Engine only (in-app tables + `CareQueueEngine`) - Tables: `care_service_queues`, `care_service_points`, `care_queue_tickets` - Service: `App\Services\Care\CareQueueEngine` @@ -34,7 +34,14 @@ - Queue marketing/settings copy now Frontdesk & POS only; `assigned_only` kept as generic routing - DemoWorld: Care receptionist apps no longer include `queue`; dedicated Queue operators (`demo-*-queue@ladill.com`) - Care upgrade banner no longer lists Ladill Queue as a Pro dependency -- Phase 5 still deletes Care’s remote `QueueClient` adapter + +## Phase 5 notes (implementation) + +- Deleted `QueueClient`, remote provisioner/bridge arms, Ladill Queue console views/presenter +- Removed `CARE_QUEUE_DRIVER` / `QUEUE_API_*` from Care config and `.env.example` +- Bridge / provisioner / specialty activate always use Care Queue Engine tables +- `care:queue-sync-tickets` remains (native backfill only) +- Tests assert local `CareQueueTicket` / `CareServiceQueue` (no Queue HTTP fakes) --- diff --git a/phpunit.xml b/phpunit.xml index 28931d0..973fa99 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -30,7 +30,6 @@ - diff --git a/resources/views/care/pro/index.blade.php b/resources/views/care/pro/index.blade.php index 729e016..61de109 100644 --- a/resources/views/care/pro/index.blade.php +++ b/resources/views/care/pro/index.blade.php @@ -102,7 +102,7 @@
  • Laboratory & pharmacy
  • Encounter billing
  • Your own Paystack / Flutterwave / Hubtel
  • -
  • Ladill Queue integration
  • +
  • In-app service queues
  • Unlimited branches (billed per branch)
  • diff --git a/resources/views/care/service-queues/_panel.blade.php b/resources/views/care/service-queues/_panel.blade.php deleted file mode 100644 index 010b404..0000000 --- a/resources/views/care/service-queues/_panel.blade.php +++ /dev/null @@ -1,145 +0,0 @@ -{{-- Compact Ladill Queue panel for role-native pages (not a standalone Service Queues nav). --}} -@php - $panel = $serviceQueuePanel ?? null; -@endphp -@if (! empty($panel['enabled'])) - @php - $counterUuid = $panel['counter_uuid'] ?? null; - $state = $panel['state'] ?? null; - $counters = $panel['counters'] ?? []; - $current = $state['current_ticket'] ?? null; - $queues = $state['queues'] ?? []; - $waiting = $state['waiting_by_queue'] ?? []; - $counter = $state['counter'] ?? []; - $stubs = $panel['stubs'] ?? []; - $statusLabel = match ($current['status'] ?? null) { - 'called' => 'Called', - 'serving' => 'Serving', - 'on_hold' => 'On hold', - default => $current['status'] ?? null, - }; - @endphp - -
    -
    -
    -

    Service counter

    -

    - {{ $counter['name'] ?? 'Ladill Queue' }} - @if (! empty($counter['branch'])) - · {{ $counter['branch'] }} - @endif -

    -
    -
    - @if (count($counters) > 1 && $counterUuid) -
    - @foreach (request()->except('counter_uuid') as $key => $value) - @if (is_scalar($value)) - - @endif - @endforeach - -
    - @endif - @if ($counterUuid) - Full console - @endif -
    -
    - -
    - @if (! empty($panel['error'])) -

    {{ $panel['error'] }}

    - @endif - - @if ($current && $counterUuid) -
    -
    -

    Now serving

    -

    {{ $current['ticket_number'] }}

    -

    {{ $current['customer_name'] ?? 'Walk-in' }} · {{ $current['queue']['name'] ?? '' }}

    -
    - @if ($statusLabel) - {{ $statusLabel }} - @endif -
    -
    - @foreach ([ - 'start' => 'Start', - 'complete' => 'Complete', - 'recall' => 'Recall', - 'skip' => 'Skip', - ] as $action => $label) -
    - @csrf - - - -
    - @endforeach -
    - @elseif ($counterUuid) -

    No active ticket. Call the next patient from a queue below.

    - @elseif (count($stubs)) -
    -

    Specialty queues ready

    -
      - @foreach ($stubs as $stub) -
    • - {{ $stub['name'] ?? 'Queue' }} - @if (! empty($stub['branch_name'])) · {{ $stub['branch_name'] }} @endif - @if (! empty($stub['prefix'])) - ({{ $stub['prefix'] }}) - @endif - @if (! empty($stub['synced'])) - · linked - @else - · create in Queue admin if missing - @endif -
    • - @endforeach -
    -
    - @elseif (! $panel['error']) -

    No counters found for this branch. Create counters in Queue admin, then refresh.

    - @endif - - @if ($counterUuid && count($queues)) -
    - @foreach ($queues as $queue) - @php $waitingTickets = $waiting[$queue['uuid']] ?? []; @endphp -
    -
    -
    -

    {{ $queue['name'] }}

    -

    {{ count($waitingTickets) }} waiting

    -
    -
    - @csrf - - - -
    -
    - @if (count($waitingTickets)) -
      - @foreach (array_slice($waitingTickets, 0, 3) as $ticket) -
    • - {{ $ticket['ticket_number'] }} - {{ $ticket['customer_name'] ?? 'Walk-in' }} -
    • - @endforeach -
    - @endif -
    - @endforeach -
    - @endif -
    -
    -@endif diff --git a/resources/views/care/service-queues/console.blade.php b/resources/views/care/service-queues/console.blade.php deleted file mode 100644 index df2fb98..0000000 --- a/resources/views/care/service-queues/console.blade.php +++ /dev/null @@ -1,142 +0,0 @@ -@php - $counter = $state['counter'] ?? []; - $current = $state['current_ticket'] ?? null; - $queues = $state['queues'] ?? []; - $waiting = $state['waiting_by_queue'] ?? []; - $transferQueues = collect($state['transfer_queues'] ?? []); - $handoffQueues = $transferQueues - ->filter(fn ($q) => ($q['uuid'] ?? '') !== ($current['queue']['uuid'] ?? '')) - ->values(); - $statusLabel = match ($current['status'] ?? null) { - 'called' => 'Called', - 'serving' => 'Serving', - 'on_hold' => 'On hold', - default => $current['status'] ?? null, - }; -@endphp - - -
    -
    - ← Patient queue -

    {{ $counter['name'] ?? 'Counter' }}

    -

    {{ $counter['branch'] ?? '' }} · Ladill Queue console

    -
    -
    - @if (! empty($counter['status'])) - {{ $counter['status'] }} - @endif -
    -
    @csrf
    -
    @csrf
    -
    -
    -
    - - @if (session('success')) -

    {{ session('success') }}

    - @endif - @if (session('error')) -

    {{ session('error') }}

    - @endif - - @if ($current) -
    -
    -

    Current customer

    -
    -
    -
    -
    -

    {{ $current['ticket_number'] }}

    -

    {{ $current['queue']['name'] ?? '' }}

    -

    {{ $current['customer_name'] ?? 'Walk-in customer' }}

    -
    - @if ($statusLabel) - {{ $statusLabel }} - @endif -
    - -
    - @foreach ([ - 'recall' => ['Recall', 'btn-secondary'], - 'start' => ['Start serving', 'btn-primary'], - 'hold' => ['Hold', 'btn-secondary'], - 'skip' => ['Skip', 'btn-secondary'], - 'complete' => ['Complete', 'btn-primary'], - 'no_show' => ['No show', 'btn-secondary'], - ] as $action => [$label, $btnClass]) -
    - @csrf - - - -
    - @endforeach -
    - - @if ($handoffQueues->isNotEmpty()) -
    - @csrf - - -
    -

    Hand off to another queue

    -

    Sends this patient to the next service while keeping ticket {{ $current['ticket_number'] }}.

    -
    -
    -
    - - -
    -
    - - -
    - -
    -
    - @endif -
    -
    - @else -
    -

    No active customer

    -

    Call the next waiting ticket from a queue below.

    -
    - @endif - -
    - @foreach ($queues as $queue) - @php $waitingTickets = $waiting[$queue['uuid']] ?? []; @endphp -
    -
    -
    -

    {{ $queue['name'] }}

    -

    {{ count($waitingTickets) }} waiting

    -
    -
    - @csrf - - - -
    -
    -
      - @forelse ($waitingTickets as $ticket) -
    • - {{ $ticket['ticket_number'] }} - {{ $ticket['customer_name'] ?? 'Walk-in' }} -
    • - @empty -
    • Queue is empty
    • - @endforelse -
    -
    - @endforeach -
    -
    diff --git a/resources/views/care/service-queues/index.blade.php b/resources/views/care/service-queues/index.blade.php deleted file mode 100644 index 25b8aa4..0000000 --- a/resources/views/care/service-queues/index.blade.php +++ /dev/null @@ -1,34 +0,0 @@ - -
    -
    -

    Service queues

    -

    Powered by Ladill Queue — call tickets and manage counters without leaving Care

    -
    - Open Queue admin -
    - - @if (session('error')) -
    {{ session('error') }}
    - @endif - -
    - @forelse ($counters as $counter) -
    -
    -
    -

    {{ $counter['name'] }}

    -

    {{ $counter['status'] ?? 'offline' }}

    -
    -
    -

    - Queues: {{ collect($counter['queues'] ?? [])->join(', ') ?: 'None assigned' }} -

    - Open console -
    - @empty -
    - No counters found in Ladill Queue. Create counters in Queue admin, then return here. -
    - @endforelse -
    -
    diff --git a/resources/views/care/settings/edit.blade.php b/resources/views/care/settings/edit.blade.php index 93efc33..feae2f1 100644 --- a/resources/views/care/settings/edit.blade.php +++ b/resources/views/care/settings/edit.blade.php @@ -134,7 +134,7 @@ - +