withoutMiddleware(EnsurePlatformSession::class); $this->owner = User::create([ 'public_id' => 'access-owner', 'name' => 'Owner', 'email' => 'access-owner@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->owner->public_id, 'name' => 'Access Clinic', 'slug' => 'access-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->modules = app(SpecialtyModuleService::class); $this->modules->ensureDefaultModulesProvisioned($this->organization, $this->owner->public_id); foreach (['cardiology', 'infusion', 'pathology', 'dentistry'] as $key) { $this->modules->activate($this->organization->fresh(), $this->owner->public_id, $key); } $this->organization->refresh(); } protected function makeStaff(string $role, string $suffix): array { $user = User::create([ 'public_id' => 'access-'.$suffix, 'name' => ucfirst($role).' '.$suffix, 'email' => $suffix.'@example.com', ]); $member = Member::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $user->public_id, 'role' => $role, 'branch_id' => $this->branch->id, ]); return [$user, $member]; } public function test_nurse_and_pharmacist_manage_general_modules(): void { [, $nurse] = $this->makeStaff('nurse', 'nurse1'); [, $pharmacist] = $this->makeStaff('pharmacist', 'rx1'); [, $lab] = $this->makeStaff('lab_technician', 'lab1'); $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'emergency')); $this->assertTrue($this->modules->memberCanManage($this->organization, $pharmacist, 'emergency')); $this->assertTrue($this->modules->memberCanManage($this->organization, $pharmacist, 'infusion')); $this->assertTrue($this->modules->memberCanManage($this->organization, $lab, 'pathology')); $this->assertTrue($this->modules->memberCanManage($this->organization, $lab, 'blood_bank')); $this->assertFalse($this->modules->memberCanManage($this->organization, $pharmacist, 'cardiology')); } public function test_gp_doctor_manages_general_but_only_views_restricted(): void { [$doctor, $member] = $this->makeStaff('doctor', 'gp1'); Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'member_id' => $member->id, 'user_ref' => $doctor->public_id, 'name' => 'Dr GP', 'specialty' => 'General Practice', 'is_active' => true, ]); $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'emergency')); $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'dentistry')); $this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'cardiology')); $this->assertTrue($this->modules->memberCanView($this->organization, $member, 'cardiology')); $this->assertTrue($this->modules->memberCanRefer($this->organization, $member, 'cardiology')); $this->assertSame('refer', $this->modules->memberAccessLevel($this->organization, $member, 'cardiology')); } public function test_cardiologist_manages_restricted_cardiology(): void { [$doctor, $member] = $this->makeStaff('doctor', 'cardio1'); $dept = Department::query()->where('type', 'cardiology')->firstOrFail(); Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'department_id' => $dept->id, 'member_id' => $member->id, 'user_ref' => $doctor->public_id, 'name' => 'Dr Cardio', 'specialty' => 'Cardiology', 'is_active' => true, ]); $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology')); $this->assertSame('manage', $this->modules->memberAccessLevel($this->organization, $member, 'cardiology')); } public function test_gp_can_refer_into_restricted_specialty_queue(): void { [$doctor, $member] = $this->makeStaff('doctor', 'gp2'); Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'member_id' => $member->id, 'user_ref' => $doctor->public_id, 'name' => 'Dr GP2', 'specialty' => 'General Practice', 'is_active' => true, ]); $patient = Patient::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-REF-1', 'first_name' => 'Ama', 'last_name' => 'Refer', 'gender' => 'female', 'date_of_birth' => '1990-01-01', ]); $this->actingAs($doctor) ->post(route('care.specialty.refer', 'cardiology'), [ 'patient_id' => $patient->id, 'branch_id' => $this->branch->id, 'reason' => 'Chest pain review', ]) ->assertRedirect(route('care.queue.index')); $this->assertDatabaseHas('care_appointments', [ 'patient_id' => $patient->id, 'reason' => 'Chest pain review', 'status' => Appointment::STATUS_WAITING, ]); } public function test_gp_cannot_save_clinical_on_restricted_module(): void { [$doctor, $member] = $this->makeStaff('doctor', 'gp3'); Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'member_id' => $member->id, 'user_ref' => $doctor->public_id, 'name' => 'Dr GP3', 'specialty' => 'General Practice', 'is_active' => true, ]); $dept = Department::query()->where('type', 'cardiology')->firstOrFail(); $patient = Patient::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-CARD-1', 'first_name' => 'Kofi', 'last_name' => 'Heart', 'gender' => 'male', 'date_of_birth' => '1980-01-01', ]); $visit = \App\Models\Visit::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'status' => \App\Models\Visit::STATUS_IN_PROGRESS, '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, 'department_id' => $dept->id, 'visit_id' => $visit->id, 'type' => Appointment::TYPE_WALK_IN, 'status' => Appointment::STATUS_IN_CONSULTATION, 'scheduled_at' => now(), 'checked_in_at' => now(), 'started_at' => now(), ]); $this->actingAs($doctor) ->get(route('care.specialty.workspace', ['module' => 'cardiology', 'visit' => $visit, 'tab' => 'exam'])) ->assertOk() ->assertSee('view + refer') ->assertDontSee('Save record') ->assertDontSee('Call next') ->assertDontSee('Add to invoice') ->assertDontSee('Upload document') ->assertSee('Refer to specialty queue') ->assertDontSee( 'action="'.route('care.specialty.consultation.start', ['module' => 'cardiology', 'visit' => $visit], false).'"', false, ); $this->actingAs($doctor) ->post(route('care.specialty.clinical.save', ['module' => 'cardiology', 'visit' => $visit]), [ 'tab' => 'exam', 'payload' => [ 'chief_complaint' => 'Should not save', ], ]) ->assertForbidden(); } public function test_sidebar_and_dashboard_exclude_modules_with_no_access(): void { [$pharmacistUser, $pharmacistMember] = $this->makeStaff('pharmacist', 'rx-nav'); $enabledForMember = collect($this->modules->enabledModulesForMember($this->organization, $pharmacistMember)) ->pluck('key') ->all(); $this->assertContains('emergency', $enabledForMember); $this->assertContains('infusion', $enabledForMember); $this->assertNotContains('cardiology', $enabledForMember); $this->assertNotContains('dentistry', $enabledForMember); $this->actingAs($pharmacistUser) ->get(route('care.dashboard')) ->assertOk() ->assertSee('Emergency') ->assertDontSee('Cardiology'); } public function test_manage_role_still_sees_mutate_actions_on_restricted_module(): void { [$doctor, $member] = $this->makeStaff('doctor', 'cardio-ui'); $dept = Department::query()->where('type', 'cardiology')->firstOrFail(); Practitioner::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'department_id' => $dept->id, 'member_id' => $member->id, 'user_ref' => $doctor->public_id, 'name' => 'Dr Cardio UI', 'specialty' => 'Cardiology', 'is_active' => true, ]); $patient = Patient::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'P-CARD-UI', 'first_name' => 'Abena', 'last_name' => 'Heart', 'gender' => 'female', 'date_of_birth' => '1985-01-01', ]); $visit = \App\Models\Visit::create([ 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $patient->id, 'status' => \App\Models\Visit::STATUS_IN_PROGRESS, '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, 'department_id' => $dept->id, 'visit_id' => $visit->id, 'type' => Appointment::TYPE_WALK_IN, 'status' => Appointment::STATUS_WAITING, 'scheduled_at' => now(), 'checked_in_at' => now(), ]); $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology')); $this->actingAs($doctor) ->get(route('care.specialty.workspace', ['module' => 'cardiology', 'visit' => $visit])) ->assertOk() ->assertDontSee('view + refer') ->assertSee('Start'); } public function test_admin_keeps_full_manage_access(): void { $admin = Member::query()->where('user_ref', $this->owner->public_id)->firstOrFail(); $this->assertTrue($this->modules->memberCanManage($this->organization, $admin, 'cardiology')); $this->assertTrue($this->modules->memberCanManage($this->organization, $admin, 'emergency')); } }