withoutMiddleware(EnsurePlatformSession::class); $this->owner = User::create([ 'public_id' => 'display-voice-owner', 'name' => 'Owner', 'email' => 'display-voice@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->owner->public_id, 'name' => 'Display Voice Clinic', 'slug' => 'display-voice-clinic', 'settings' => [ 'onboarded' => true, 'facility_type' => 'clinic', 'plan' => 'pro', 'plan_expires_at' => now()->addMonth()->toIso8601String(), 'queue_integration_enabled' => true, ], ]); Member::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, 'role' => 'hospital_admin', ]); $this->branch = Branch::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'name' => 'Main', 'is_active' => true, ]); $this->queue = CareServiceQueue::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'context' => 'consultation', 'name' => 'Reception', 'prefix' => 'A', 'routing_mode' => 'shared_pool', 'is_active' => true, ]); $this->point = CareServicePoint::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'service_queue_id' => $this->queue->id, 'name' => 'Counter 1', 'is_active' => true, ]); } public function test_display_data_includes_pending_announcement_after_call(): void { $screen = CareDisplayScreen::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Lobby', 'layout' => 'standard', 'service_queue_ids' => [$this->queue->id], 'is_active' => true, ]); $engine = app(CareQueueEngine::class); $engine->issueTicket($this->organization, [ 'service_queue_id' => $this->queue->uuid, ]); $engine->callNext($this->organization, $this->queue->uuid, $this->point->uuid); $response = $this->getJson(route('care.display.data', $screen->access_token)); $response->assertOk() ->assertJsonPath('announcements.0.message', 'Ticket A001, please proceed to Counter 1.') ->assertJsonPath('waiting_count', 0) ->assertJsonPath('now_serving.0.ticket_number', 'A001') ->assertJsonPath('now_serving.0.queue_name', 'Reception') ->assertJsonPath('now_serving.0.counter', 'Counter 1'); $this->get(route('care.display.public', $screen->access_token)) ->assertOk() ->assertSee('Please proceed to') ->assertSee('A001') ->assertSee('Reception') ->assertSee('Counter 1'); } public function test_display_shows_only_latest_ticket_per_service_point(): void { $room12 = CareServicePoint::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'service_queue_id' => $this->queue->id, 'name' => 'Room 12', 'is_active' => true, ]); $room14 = CareServicePoint::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'service_queue_id' => $this->queue->id, 'name' => 'Room 14', 'is_active' => true, ]); $screen = CareDisplayScreen::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Busy Lobby', 'layout' => 'standard', 'service_queue_ids' => [$this->queue->id], 'is_active' => true, ]); foreach ([ ['A001', $room12->id, 6], ['A002', $room12->id, 5], ['A003', $room12->id, 2], ['A004', $room14->id, 4], ['A005', $room14->id, 1], ] as [$number, $pointId, $minutesAgo]) { CareQueueTicket::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'service_queue_id' => $this->queue->id, 'service_point_id' => $pointId, 'ticket_number' => $number, 'status' => CareQueueTicket::STATUS_CALLED, 'called_at' => now()->subMinutes($minutesAgo), ]); } $payload = $this->getJson(route('care.display.data', $screen->access_token)) ->assertOk() ->assertJsonCount(2, 'now_serving') ->json('now_serving'); $byCounter = collect($payload)->keyBy('counter'); $this->assertSame('A003', $byCounter['Room 12']['ticket_number']); $this->assertSame('A005', $byCounter['Room 14']['ticket_number']); $this->assertSame(['Room 12', 'Room 14'], collect($payload)->pluck('counter')->all()); } public function test_display_falls_back_to_branch_queues_when_none_assigned(): void { $screen = CareDisplayScreen::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Lobby', 'layout' => 'standard', 'service_queue_ids' => [], 'is_active' => true, ]); app(CareQueueEngine::class)->issueTicket($this->organization, [ 'service_queue_id' => $this->queue->uuid, ]); $this->getJson(route('care.display.data', $screen->access_token)) ->assertOk() ->assertJsonPath('waiting_count', 1) ->assertJsonPath('queues.0.name', 'Reception'); } public function test_display_marks_announcement_played(): void { $screen = CareDisplayScreen::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Lobby', 'layout' => 'standard', 'service_queue_ids' => [$this->queue->id], 'is_active' => true, ]); $announcement = CareVoiceAnnouncement::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'locale' => 'en', 'message' => 'Ticket A001, please proceed to Counter 1.', 'status' => 'pending', ]); $this->postJson(route('care.display.announcement.played', [ 'token' => $screen->access_token, 'announcement' => $announcement->id, ]))->assertOk(); $this->assertSame('played', $announcement->fresh()->status); } }