withoutMiddleware(EnsurePlatformSession::class); $this->owner = User::create([ 'public_id' => 'queue-perf-owner', 'name' => 'Owner', 'email' => 'queue-perf@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->owner->public_id, 'name' => 'Perf Clinic', 'slug' => 'perf-clinic', 'settings' => [ 'onboarded' => true, 'facility_type' => 'clinic', 'plan' => 'pro', 'plan_expires_at' => now()->addMonth()->toIso8601String(), 'queue_integration_enabled' => true, 'rollout' => [ CareFeatures::WORKFLOW_ENGINE => true, CareFeatures::FINANCIAL_GATES => true, ], ], ]); Member::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, 'role' => 'receptionist', ]); $this->branch = Branch::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'name' => 'Main', 'is_active' => true, ]); } public function test_queue_index_does_not_backfill_tickets_or_start_workflows(): void { $workflow = FacilityWorkflow::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Default', 'is_active' => true, ]); WorkflowStage::create([ 'owner_ref' => $this->owner->public_id, 'workflow_id' => $workflow->id, 'code' => 'consultation', 'name' => 'Consultation', 'type' => 'consultation', 'queue_context' => CareQueueContexts::CONSULTATION, 'sort_order' => 1, 'requires_payment' => false, ]); $seedWaiters = function (int $count, int $offset = 0): void { for ($i = 0; $i < $count; $i++) { $n = $offset + $i; $patient = Patient::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-PERF-'.$n, 'first_name' => 'Pat', 'last_name' => (string) $n, ]); $visit = Visit::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'status' => Visit::STATUS_OPEN, 'checked_in_at' => now(), ]); Appointment::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'visit_id' => $visit->id, 'type' => Appointment::TYPE_WALK_IN, 'status' => Appointment::STATUS_WAITING, 'scheduled_at' => now(), 'waiting_at' => now()->subMinutes(100 - $n), 'queue_position' => $n + 1, ]); } }; $seedWaiters(5); DB::flushQueryLog(); DB::enableQueryLog(); $this->actingAs($this->owner) ->get(route('care.queue.index', ['branch_id' => $this->branch->id])) ->assertOk(); $queriesWith5 = count(DB::getQueryLog()); DB::disableQueryLog(); $seedWaiters(20, 5); DB::flushQueryLog(); DB::enableQueryLog(); $this->actingAs($this->owner) ->get(route('care.queue.index', ['branch_id' => $this->branch->id])) ->assertOk() ->assertSee('Patient flow'); $log = collect(DB::getQueryLog()); DB::disableQueryLog(); $queriesWith25 = $log->count(); $this->assertSame(0, Appointment::query()->whereNotNull('queue_ticket_uuid')->where('queue_ticket_uuid', '!=', '')->count()); $this->assertSame(0, VisitStageAdvance::query()->count(), 'Board GET must not start workflows'); // Adding 20 waiters must not add ~N queries (gate refresh / ticket sync / context loops). $delta = $queriesWith25 - $queriesWith5; $this->assertLessThan( 25, $delta, "Query delta for +20 waiters should be small (eager loads), got {$delta} (5={$queriesWith5}, 25={$queriesWith25})", ); $advanceLookups = $log->filter( fn ($q) => str_contains(strtolower($q['query']), 'care_visit_stage_advances'), )->count(); $this->assertLessThanOrEqual( 3, $advanceLookups, "visit_stage_advances lookups should be eager-loaded, got {$advanceLookups}", ); } public function test_queue_board_caps_waiting_list_size(): void { $limit = AppointmentService::QUEUE_BOARD_LIMIT; for ($i = 0; $i < $limit + 15; $i++) { $patient = Patient::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-CAP-'.$i, 'first_name' => 'Cap', 'last_name' => (string) $i, ]); Appointment::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'type' => Appointment::TYPE_WALK_IN, 'status' => Appointment::STATUS_WAITING, 'scheduled_at' => now(), 'waiting_at' => now()->subMinutes($limit + 15 - $i), 'queue_position' => $i + 1, ]); } $queue = app(AppointmentService::class)->queue( $this->owner->public_id, $this->branch->id, ); $this->assertCount($limit, $queue); $this->assertSame(1, (int) $queue->first()->queue_position); } public function test_display_gate_hides_blocked_visits_without_refresh(): void { $workflow = FacilityWorkflow::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Gated', 'is_active' => true, ]); $stage = WorkflowStage::create([ 'owner_ref' => $this->owner->public_id, 'workflow_id' => $workflow->id, 'code' => 'consultation', 'name' => 'Consultation', 'type' => 'consultation', 'queue_context' => CareQueueContexts::CONSULTATION, 'sort_order' => 1, 'requires_payment' => true, 'payment_timing' => 'before', 'default_amount_minor' => 1000, ]); $patient = Patient::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-GATE', 'first_name' => 'Blocked', 'last_name' => 'Patient', ]); $visit = Visit::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'status' => Visit::STATUS_OPEN, 'checked_in_at' => now(), ]); VisitStageAdvance::create([ 'owner_ref' => $this->owner->public_id, 'visit_id' => $visit->id, 'workflow_id' => $workflow->id, 'workflow_stage_id' => $stage->id, 'stage_code' => 'consultation', 'status' => VisitStageAdvance::STATUS_BLOCKED, 'blocked_reason' => 'awaiting_payment', 'entered_at' => now(), ]); $gate = app(WorkflowQueueGate::class); $this->assertFalse($gate->isReleasedForDisplay( $this->organization, $visit->fresh('currentStageAdvance.stage'), CareQueueContexts::CONSULTATION, )); } }