withoutMiddleware(EnsurePlatformSession::class); config([ 'care.queue.api_url' => 'https://queue.test/api/v1', 'care.queue.api_key' => 'care-test-key', ]); $this->owner = User::create([ 'public_id' => 'sp-care-owner', 'name' => 'Owner', 'email' => 'sp-care@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->owner->public_id, 'name' => 'Ridge Medical Centre', 'slug' => 'ridge-medical', 'settings' => [ 'onboarded' => true, 'facility_type' => 'hospital', '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->doctorA = Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Dr. Mensah', 'specialty' => 'General', 'room' => 'Consultation Room 4', 'user_ref' => 'doc-mensah', 'is_active' => true, ]); $this->doctorB = Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'name' => 'Dr. Okai', 'specialty' => 'Pediatrics', 'room' => 'Consultation Room 5', 'user_ref' => 'doc-okai', 'is_active' => true, ]); $settings = $this->organization->settings; $settings['department_queue_provisioning'] = [ CareQueueContexts::CONSULTATION => [ 'queues' => [[ 'branch_id' => $this->branch->id, 'branch_name' => 'Main', 'name' => 'Consultation', 'prefix' => 'C', 'active' => true, 'synced' => true, 'routing_mode' => 'assigned_only', 'queue_uuid' => 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'queue_external_key' => CareQueueContexts::queueExternalKey(CareQueueContexts::CONSULTATION, $this->branch->id), 'points' => [ [ 'kind' => 'practitioner', 'ref_id' => $this->doctorA->id, 'name' => 'Dr. Mensah', 'destination' => 'Consultation Room 4', 'staff_display_name' => 'Dr. Mensah', 'external_key' => CareQueueContexts::pointExternalKey( CareQueueContexts::CONSULTATION, $this->branch->id, 'practitioner', $this->doctorA->id, ), 'counter_uuid' => '11111111-1111-1111-1111-111111111111', 'synced' => true, ], [ 'kind' => 'practitioner', 'ref_id' => $this->doctorB->id, 'name' => 'Dr. Okai', 'destination' => 'Consultation Room 5', 'staff_display_name' => 'Dr. Okai', 'external_key' => CareQueueContexts::pointExternalKey( CareQueueContexts::CONSULTATION, $this->branch->id, 'practitioner', $this->doctorB->id, ), 'counter_uuid' => '22222222-2222-2222-2222-222222222222', 'synced' => true, ], ], ]], ], CareQueueContexts::PHARMACY => [ 'queues' => [[ 'branch_id' => $this->branch->id, 'name' => 'Pharmacy', 'prefix' => 'P', 'active' => true, 'synced' => true, 'routing_mode' => 'assigned_only', 'queue_uuid' => 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'points' => [[ 'kind' => 'member', 'ref_id' => 1, 'destination' => 'Pharmacy counter 1', 'counter_uuid' => '33333333-3333-3333-3333-333333333333', 'synced' => true, ]], ]], ], ]; $this->organization->update(['settings' => $settings]); } public function test_fixed_doctor_appointment_issues_to_that_doctors_point(): void { Http::fake(function (\Illuminate\Http\Client\Request $request) { if ($request->method() === 'POST' && str_contains($request->url(), '/tickets')) { $this->assertSame('11111111-1111-1111-1111-111111111111', $request['assigned_counter_id'] ?? null); $this->assertSame('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', $request['service_queue_id'] ?? null); return Http::response([ 'data' => [ 'uuid' => (string) Str::uuid(), 'ticket_number' => 'C001', 'status' => 'waiting', 'destination' => 'Consultation Room 4', 'staff_display_name' => 'Dr. Mensah', 'assigned_counter' => [ 'uuid' => '11111111-1111-1111-1111-111111111111', 'destination' => 'Consultation Room 4', 'staff_display_name' => 'Dr. Mensah', ], ], ], 201); } return Http::response(['message' => 'unexpected'], 500); }); $patient = Patient::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-1', 'first_name' => 'Ada', 'last_name' => 'Lovelace', ]); $appointment = Appointment::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'practitioner_id' => $this->doctorA->id, 'type' => Appointment::TYPE_SCHEDULED, 'status' => Appointment::STATUS_SCHEDULED, 'scheduled_at' => now()->addHour(), ]); $result = app(AppointmentService::class)->checkIn($appointment, $this->owner->public_id); $this->assertSame('C001', $result->queue_ticket_number); $this->assertSame('11111111-1111-1111-1111-111111111111', $result->queue_service_point_uuid); $this->assertSame('Consultation Room 4', $result->queue_destination); $this->assertSame('Dr. Mensah', $result->queue_staff_display_name); $this->assertSame(CareQueueContexts::ROUTING_ROUTED, $result->queue_routing_status); } public function test_walk_in_without_doctor_flags_unresolved_and_does_not_issue(): void { Http::fake(); $patient = Patient::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-2', 'first_name' => 'Bob', 'last_name' => 'Builder', ]); $appointment = Appointment::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'practitioner_id' => null, 'type' => Appointment::TYPE_WALK_IN, 'status' => Appointment::STATUS_WAITING, 'waiting_at' => now(), ]); $result = app(CareQueueBridge::class)->issueForAppointment($this->organization, $appointment); $this->assertNull($result->queue_ticket_uuid); $this->assertSame(CareQueueContexts::ROUTING_UNRESOLVED, $result->queue_routing_status); Http::assertNothingSent(); } public function test_walk_in_with_selected_doctor_routes_only_to_that_point(): void { Http::fake(function (\Illuminate\Http\Client\Request $request) { if ($request->method() === 'POST' && str_contains($request->url(), '/tickets')) { $this->assertSame('22222222-2222-2222-2222-222222222222', $request['assigned_counter_id'] ?? null); return Http::response([ 'data' => [ 'uuid' => (string) Str::uuid(), 'ticket_number' => 'C002', 'status' => 'waiting', 'assigned_counter' => ['uuid' => '22222222-2222-2222-2222-222222222222'], 'destination' => 'Consultation Room 5', 'staff_display_name' => 'Dr. Okai', ], ], 201); } return Http::response(['message' => 'unexpected'], 500); }); $patient = Patient::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-3', 'first_name' => 'Cara', 'last_name' => 'Clinic', ]); $appointment = app(AppointmentService::class)->walkIn( $this->organization, $this->owner->public_id, [ 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'practitioner_id' => $this->doctorB->id, 'reason' => 'Fever', ], $this->owner->public_id, ); $this->assertSame($this->doctorB->id, $appointment->practitioner_id); $this->assertSame('C002', $appointment->queue_ticket_number); $this->assertSame('Consultation Room 5', $appointment->queue_destination); } public function test_pharmacist_call_next_uses_pharmacy_point(): void { $pharmacist = Member::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => 'pharm-1', 'role' => 'pharmacist', 'branch_id' => $this->branch->id, ]); $settings = $this->organization->settings; $settings['department_queue_provisioning'][CareQueueContexts::PHARMACY]['queues'][0]['points'] = [[ 'kind' => 'member', 'ref_id' => $pharmacist->id, 'destination' => 'Pharmacy counter 1', 'staff_ref' => 'pharm-1', 'counter_uuid' => '33333333-3333-3333-3333-333333333333', 'synced' => true, ]]; $this->organization->update(['settings' => $settings]); Http::fake(function (\Illuminate\Http\Client\Request $request) { if (str_contains($request->url(), '/call-next')) { $this->assertStringContainsString('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', $request->url()); $this->assertSame('33333333-3333-3333-3333-333333333333', $request['counter_id'] ?? null); return Http::response([ 'data' => [ 'uuid' => (string) Str::uuid(), 'ticket_number' => 'P001', 'status' => 'called', ], ], 200); } return Http::response(['message' => 'unexpected'], 500); }); $result = app(CareQueueBridge::class)->callNext( $this->organization->fresh(), CareQueueContexts::PHARMACY, $this->branch->id, $pharmacist, ); $this->assertSame('P001', $result['ticket']['ticket_number'] ?? null); } public function test_integration_off_preserves_behavior_without_ticket_issuance(): void { $settings = $this->organization->settings; $settings['queue_integration_enabled'] = false; $this->organization->update(['settings' => $settings]); Http::fake(); $patient = Patient::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-4', 'first_name' => 'Dee', 'last_name' => 'Demo', ]); $appointment = Appointment::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'practitioner_id' => $this->doctorA->id, 'type' => Appointment::TYPE_SCHEDULED, 'status' => Appointment::STATUS_SCHEDULED, 'scheduled_at' => now()->addHour(), ]); $result = app(AppointmentService::class)->checkIn($appointment, $this->owner->public_id); $this->assertNull($result->queue_ticket_uuid); Http::assertNothingSent(); } public function test_provisioner_builds_one_point_per_practitioner_idempotently(): void { $calls = ['queues' => 0, 'counters' => 0]; Http::fake(function (\Illuminate\Http\Client\Request $request) use (&$calls) { if (str_contains($request->url(), '/integrations/status')) { return Http::response(['data' => ['provisioned' => true, 'integrations' => ['care' => true]]], 200); } if (str_contains($request->url(), '/branches') && $request->method() === 'POST') { return Http::response(['data' => ['name' => 'Main']], 201); } if (str_contains($request->url(), '/departments') && $request->method() === 'POST') { return Http::response(['data' => ['id' => 9, 'name' => $request['name'] ?? 'Dept']], 201); } if (str_contains($request->url(), '/queues') && $request->method() === 'POST' && ! str_contains($request->url(), 'call-next')) { $calls['queues']++; $this->assertSame('assigned_only', $request['routing_mode'] ?? null); return Http::response([ 'data' => [ 'uuid' => (string) Str::uuid(), 'name' => $request['name'] ?? 'Q', 'external_key' => $request['external_key'] ?? null, ], ], 201); } if (str_contains($request->url(), '/counters') && $request->method() === 'POST') { $calls['counters']++; return Http::response([ 'data' => [ 'uuid' => (string) Str::uuid(), 'name' => $request['name'] ?? 'C', 'destination' => $request['destination'] ?? null, ], ], 201); } return Http::response(['message' => 'unexpected '.$request->url()], 500); }); // Clear prior stub so provisioner builds fresh. $settings = $this->organization->settings; unset($settings['department_queue_provisioning']); $this->organization->update(['settings' => $settings]); app(CareQueueProvisioner::class)->provisionOrganization($this->organization->fresh()); $first = data_get($this->organization->fresh()->settings, 'department_queue_provisioning.consultation.queues.0.points'); $this->assertIsArray($first); $this->assertGreaterThanOrEqual(2, count($first)); app(CareQueueProvisioner::class)->provisionOrganization($this->organization->fresh()); $second = data_get($this->organization->fresh()->settings, 'department_queue_provisioning.consultation.queues.0.points'); $this->assertCount(count($first), $second); } }