Deploy Ladill Care / deploy (push) Successful in 57s
Replace broad doctor/nurse specialty access with granular roles and primary apps, permission inheritance for lab/BB managers, and cannot-rules for discharge, lab approve, and cashier vs billing officer. Co-authored-by: Cursor <cursoragent@cursor.com>
1101 lines
46 KiB
PHP
1101 lines
46 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Middleware\EnsurePlatformSession;
|
|
use App\Models\Appointment;
|
|
use App\Models\Branch;
|
|
use App\Models\Consultation;
|
|
use App\Models\Department;
|
|
use App\Models\Member;
|
|
use App\Models\Organization;
|
|
use App\Models\Patient;
|
|
use App\Models\Practitioner;
|
|
use App\Models\User;
|
|
use App\Services\Care\SpecialtyModuleService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use Tests\TestCase;
|
|
|
|
class CareSpecialtyAccessTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $owner;
|
|
|
|
protected Organization $organization;
|
|
|
|
protected Branch $branch;
|
|
|
|
protected SpecialtyModuleService $modules;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->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', 'psychiatry', 'surgery', 'radiology'] 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');
|
|
[, $edNurse] = $this->makeStaff('ed_nurse', 'edn1');
|
|
[, $pharmacist] = $this->makeStaff('pharmacist', 'rx1');
|
|
[, $lab] = $this->makeStaff('lab_technician', 'lab1');
|
|
|
|
// Legacy nurse is limited — no specialty modules.
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $nurse, 'emergency'));
|
|
// ED nurse matrix apps: Emergency, Blood Bank, Radiology, Pathology.
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $edNurse, 'emergency'));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $edNurse, 'pathology'));
|
|
// Pharmacist is pharmacy-only (core app) — no specialty modules.
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $pharmacist, 'emergency'));
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $pharmacist, 'infusion'));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $lab, 'pathology'));
|
|
$this->assertFalse($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('general_physician', '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->assertFalse($this->modules->isDeskSpecialist($this->organization, $member));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'emergency'));
|
|
// Dentistry is not a GP primary app.
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'dentistry'));
|
|
// Cardiology is a GP primary app (manage).
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology'));
|
|
// Psychiatry is outside GP apps — refer/view via legacy config for GP.
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'psychiatry'));
|
|
$this->assertTrue($this->modules->memberCanView($this->organization, $member, 'psychiatry'));
|
|
$this->assertTrue($this->modules->memberCanRefer($this->organization, $member, 'psychiatry'));
|
|
$this->assertSame('refer', $this->modules->memberAccessLevel($this->organization, $member, 'psychiatry'));
|
|
}
|
|
|
|
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'));
|
|
// Desk specialists do not keep GP-wide general/limited manage.
|
|
$this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'emergency'));
|
|
$this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'dentistry'));
|
|
}
|
|
|
|
public function test_dentistry_specialist_only_sees_matching_modules(): void
|
|
{
|
|
[$doctor, $member] = $this->makeStaff('doctor', 'dentist1');
|
|
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 Dentist',
|
|
'specialty' => 'Dentistry',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$this->assertTrue($this->modules->isDeskSpecialist($this->organization, $member));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'dentistry'));
|
|
$this->assertSame('manage', $this->modules->memberAccessLevel($this->organization, $member, 'dentistry'));
|
|
|
|
foreach (['emergency', 'blood_bank', 'cardiology', 'infusion', 'pathology'] as $key) {
|
|
$this->assertSame(
|
|
'none',
|
|
$this->modules->memberAccessLevel($this->organization, $member, $key),
|
|
"Dentistry specialist should not see [{$key}]",
|
|
);
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $member, $key));
|
|
$this->assertFalse($this->modules->memberCanView($this->organization, $member, $key));
|
|
$this->assertFalse($this->modules->memberCanRefer($this->organization, $member, $key));
|
|
}
|
|
|
|
$enabledKeys = collect($this->modules->enabledModulesForMember($this->organization, $member))
|
|
->pluck('key')
|
|
->all();
|
|
$this->assertContains('dentistry', $enabledKeys);
|
|
$this->assertNotContains('emergency', $enabledKeys);
|
|
$this->assertNotContains('blood_bank', $enabledKeys);
|
|
$this->assertNotContains('cardiology', $enabledKeys);
|
|
|
|
// Single-desk specialists: no Specialty sidebar group (dashboard cards remain).
|
|
$this->assertFalse($this->modules->shouldShowSpecialtyNav($this->organization, $member));
|
|
|
|
$this->actingAs($doctor)
|
|
->get(route('care.dashboard'))
|
|
->assertOk()
|
|
->assertSee('Dentistry')
|
|
->assertSee('Specialty modules')
|
|
->assertDontSee('>Specialty</p>', false)
|
|
->assertDontSee('Emergency')
|
|
->assertDontSee('Cardiology')
|
|
->assertDontSee('Blood Bank');
|
|
}
|
|
|
|
public function test_gp_keeps_specialty_sidebar_nav(): void
|
|
{
|
|
[$doctor, $member] = $this->makeStaff('doctor', 'gp-nav');
|
|
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 Nav',
|
|
'specialty' => 'General Practice',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$this->assertFalse($this->modules->isDeskSpecialist($this->organization, $member));
|
|
$this->assertTrue($this->modules->shouldShowSpecialtyNav($this->organization, $member));
|
|
|
|
$this->actingAs($doctor)
|
|
->get(route('care.dashboard'))
|
|
->assertOk()
|
|
->assertSee('>Specialty</p>', false)
|
|
->assertSee('Emergency')
|
|
->assertSee('Cardiology')
|
|
->assertDontSee('Dentistry');
|
|
}
|
|
|
|
public function test_dentistry_specialty_does_not_match_ent_keyword(): void
|
|
{
|
|
[$doctor, $member] = $this->makeStaff('doctor', 'dentist-ent-trap');
|
|
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 Dentist',
|
|
'specialty' => 'Dentistry',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$this->assertTrue($this->modules->specialistBelongsToModule($this->organization, $member, 'dentistry'));
|
|
$this->assertFalse($this->modules->specialistBelongsToModule($this->organization, $member, 'ent'));
|
|
$this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'ent'));
|
|
}
|
|
|
|
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.specialty.show', 'cardiology'));
|
|
|
|
$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('general_physician', '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', 'psychiatry')->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-PSY-1',
|
|
'first_name' => 'Kofi',
|
|
'last_name' => 'Mind',
|
|
'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' => 'psychiatry', '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' => 'psychiatry', 'visit' => $visit], false).'"',
|
|
false,
|
|
);
|
|
|
|
$this->actingAs($doctor)
|
|
->post(route('care.specialty.clinical.save', ['module' => 'psychiatry', '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');
|
|
[, $epMember] = $this->makeStaff('emergency_physician', 'ep-nav');
|
|
|
|
$enabledForPharmacist = collect($this->modules->enabledModulesForMember($this->organization, $pharmacistMember))
|
|
->pluck('key')
|
|
->all();
|
|
$enabledForEp = collect($this->modules->enabledModulesForMember($this->organization, $epMember))
|
|
->pluck('key')
|
|
->all();
|
|
|
|
// Pharmacist: pharmacy core only — no specialty modules.
|
|
$this->assertSame([], $enabledForPharmacist);
|
|
$this->assertFalse($this->modules->shouldShowSpecialtyNav($this->organization, $pharmacistMember));
|
|
|
|
$this->assertContains('emergency', $enabledForEp);
|
|
$this->assertContains('radiology', $enabledForEp);
|
|
$this->assertNotContains('dentistry', $enabledForEp);
|
|
$this->assertTrue($this->modules->shouldShowSpecialtyNav($this->organization, $epMember));
|
|
|
|
$this->actingAs($pharmacistUser)
|
|
->get(route('care.dashboard'))
|
|
->assertOk()
|
|
->assertDontSee('>Specialty</p>', false);
|
|
}
|
|
|
|
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'));
|
|
}
|
|
|
|
/**
|
|
* @return array{0: \App\Models\User, 1: Member, 2: \App\Models\Visit}
|
|
*/
|
|
protected function specialtyVisitForRole(string $role, string $module, string $suffix): array
|
|
{
|
|
if ($module === 'dentistry') {
|
|
return $this->dentistryVisitForRole($role, $suffix);
|
|
}
|
|
if ($module === 'emergency') {
|
|
return $this->emergencyVisitForRole($role, $suffix);
|
|
}
|
|
|
|
[$user, $member] = $this->makeStaff($role, $suffix);
|
|
$definition = $this->modules->definition($module);
|
|
$deptType = (string) ($definition['department_type'] ?? $module);
|
|
$dept = Department::query()->where('type', $deptType)->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-'.strtoupper(substr($module, 0, 3)).'-'.$suffix,
|
|
'first_name' => 'Floor',
|
|
'last_name' => ucfirst($module),
|
|
'gender' => 'female',
|
|
'date_of_birth' => '1990-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(),
|
|
'specialty_stage' => 'waiting',
|
|
]);
|
|
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(),
|
|
'waiting_at' => now(),
|
|
]);
|
|
|
|
return [$user, $member, $visit];
|
|
}
|
|
|
|
/**
|
|
* @return array{0: \App\Models\User, 1: Member, 2: \App\Models\Visit}
|
|
*/
|
|
protected function dentistryVisitForRole(string $role, string $suffix): array
|
|
{
|
|
[$user, $member] = $this->makeStaff($role, $suffix);
|
|
if (in_array($role, ['doctor', 'dentist'], true)) {
|
|
Practitioner::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'member_id' => $member->id,
|
|
'user_ref' => $user->public_id,
|
|
'name' => 'Dr '.$suffix,
|
|
'specialty' => 'Dentistry',
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
$dept = Department::query()->where('type', 'dental')->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-DEN-'.$suffix,
|
|
'first_name' => 'Efua',
|
|
'last_name' => 'Boateng',
|
|
'gender' => 'female',
|
|
'date_of_birth' => '1992-06-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(),
|
|
'specialty_stage' => 'waiting',
|
|
]);
|
|
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(),
|
|
'waiting_at' => now(),
|
|
]);
|
|
|
|
return [$user, $member, $visit];
|
|
}
|
|
|
|
/**
|
|
* @return array{0: \App\Models\User, 1: Member, 2: \App\Models\Visit}
|
|
*/
|
|
protected function emergencyVisitForRole(string $role, string $suffix): array
|
|
{
|
|
[$user, $member] = $this->makeStaff($role, $suffix);
|
|
|
|
$dept = Department::query()->where('type', 'emergency')->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-ER-'.$suffix,
|
|
'first_name' => 'Kojo',
|
|
'last_name' => 'Floor',
|
|
'gender' => 'male',
|
|
'date_of_birth' => '1988-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(),
|
|
'specialty_stage' => 'arrival',
|
|
]);
|
|
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(),
|
|
'waiting_at' => now(),
|
|
]);
|
|
|
|
return [$user, $member, $visit];
|
|
}
|
|
|
|
/**
|
|
* Roles that can manage a specialty module but lack consultations.manage must not see
|
|
* Start / stage / chart-mutate CTAs. Gating is ability-based — not nurse-hardcoded.
|
|
*
|
|
* @return list<array{0: string, 1: string, 2: string}>
|
|
*/
|
|
public static function floorRolesWithoutConsultManage(): array
|
|
{
|
|
return [
|
|
['ed_nurse', 'emergency', 'floor-edn-er'],
|
|
['theatre_nurse', 'surgery', 'floor-tn-sur'],
|
|
['lab_technician', 'pathology', 'floor-lab-path'],
|
|
['radiographer', 'radiology', 'floor-rad-tech'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('floorRolesWithoutConsultManage')]
|
|
public function test_floor_roles_without_consult_do_not_see_mutate_ctas(
|
|
string $role,
|
|
string $module,
|
|
string $suffix,
|
|
): void {
|
|
[$user, $member, $visit] = $this->specialtyVisitForRole($role, $module, $suffix);
|
|
|
|
$permissions = app(\App\Services\Care\CarePermissions::class);
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $member, $module));
|
|
$this->assertFalse($permissions->can($member, 'consultations.manage'));
|
|
// Prove gating is not nurse-only: same CTA rules for every floor role without consult.
|
|
$this->assertNotSame('hospital_admin', $member->role);
|
|
|
|
$html = $this->actingAs($user)
|
|
->get(route('care.specialty.workspace', ['module' => $module, 'visit' => $visit, 'tab' => 'overview']))
|
|
->assertOk()
|
|
->assertSee('consultation access')
|
|
->assertDontSee('Seat at chair')
|
|
->assertDontSee('Start triage')
|
|
->assertSee('Patient chart')
|
|
->getContent();
|
|
|
|
// Nurses keep specialty workspaces but lose the patient-flow board link.
|
|
if ($permissions->canAccessPatientQueue($member)) {
|
|
$this->assertStringContainsString('Back to queue', $html);
|
|
$this->assertStringNotContainsString('Specialty home', $html);
|
|
} else {
|
|
$this->assertStringNotContainsString('Back to queue', $html);
|
|
$this->assertStringContainsString('Specialty home', $html);
|
|
}
|
|
|
|
$this->assertStringNotContainsString(
|
|
route('care.specialty.consultation.start', ['module' => $module, 'visit' => $visit], absolute: false),
|
|
$html,
|
|
);
|
|
|
|
if ($module === 'dentistry') {
|
|
$this->assertStringNotContainsString(
|
|
route('care.specialty.dentistry.stage', $visit, absolute: false),
|
|
$html,
|
|
);
|
|
$this->assertStringContainsString('Waiting', $html);
|
|
} elseif ($module === 'emergency') {
|
|
$this->assertStringNotContainsString(
|
|
route('care.specialty.emergency.stage', $visit, absolute: false),
|
|
$html,
|
|
);
|
|
}
|
|
|
|
if ($permissions->can($member, 'appointments.manage')) {
|
|
$this->assertStringContainsString('Register / book', $html);
|
|
} else {
|
|
$this->assertStringNotContainsString('Register / book', $html);
|
|
}
|
|
}
|
|
|
|
public function test_nurse_dentistry_mutate_posts_are_forbidden(): void
|
|
{
|
|
[$nurseUser, , $visit] = $this->dentistryVisitForRole('nurse', 'den-nurse-post');
|
|
|
|
$this->actingAs($nurseUser)
|
|
->post(route('care.specialty.dentistry.stage', $visit), ['stage' => 'chair'])
|
|
->assertForbidden();
|
|
|
|
$this->actingAs($nurseUser)
|
|
->post(route('care.specialty.consultation.start', ['module' => 'dentistry', 'visit' => $visit]))
|
|
->assertForbidden();
|
|
|
|
$this->actingAs($nurseUser)
|
|
->post(route('care.specialty.dentistry.tooth', $visit), [
|
|
'tooth_code' => '16',
|
|
'status' => 'present',
|
|
])
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_receptionist_dentistry_mutate_posts_are_forbidden(): void
|
|
{
|
|
[$user, , $visit] = $this->dentistryVisitForRole('receptionist', 'den-recv-post');
|
|
|
|
$this->actingAs($user)
|
|
->post(route('care.specialty.dentistry.stage', $visit), ['stage' => 'chair'])
|
|
->assertForbidden();
|
|
|
|
$this->actingAs($user)
|
|
->post(route('care.specialty.consultation.start', ['module' => 'dentistry', 'visit' => $visit]))
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_lab_technician_and_pharmacist_emergency_mutate_posts_are_forbidden(): void
|
|
{
|
|
foreach (['lab_technician' => 'lab-er-post', 'pharmacist' => 'rx-er-post', 'nurse' => 'nurse-er-post'] as $role => $suffix) {
|
|
[$user, , $visit] = $this->emergencyVisitForRole($role, $suffix);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('care.specialty.emergency.stage', $visit), ['stage' => 'treatment'])
|
|
->assertForbidden();
|
|
|
|
$this->actingAs($user)
|
|
->post(route('care.specialty.consultation.start', ['module' => 'emergency', 'visit' => $visit]))
|
|
->assertForbidden();
|
|
}
|
|
}
|
|
|
|
public function test_ed_nurse_cannot_discharge_emergency_visit(): void
|
|
{
|
|
[$user, $member, $visit] = $this->emergencyVisitForRole('ed_nurse', 'edn-discharge');
|
|
$permissions = app(\App\Services\Care\CarePermissions::class);
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'emergency'));
|
|
$this->assertFalse($permissions->can($member, 'emergency.discharge'));
|
|
|
|
$this->actingAs($user)
|
|
->post(route('care.specialty.emergency.disposition', $visit), [
|
|
'payload' => [
|
|
'disposition' => 'Discharged home',
|
|
'destination' => 'Home',
|
|
'medications' => 'None',
|
|
],
|
|
])
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_rbac_matrix_smoke_key_roles(): void
|
|
{
|
|
$permissions = app(\App\Services\Care\CarePermissions::class);
|
|
|
|
[, $ep] = $this->makeStaff('emergency_physician', 'matrix-ep');
|
|
[, $dentist] = $this->makeStaff('dentist', 'matrix-den');
|
|
[, $cashier] = $this->makeStaff('cashier', 'matrix-cash');
|
|
[, $lab] = $this->makeStaff('lab_technician', 'matrix-lab');
|
|
[, $labMgr] = $this->makeStaff('lab_manager', 'matrix-labm');
|
|
[, $billing] = $this->makeStaff('billing_officer', 'matrix-bill');
|
|
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $ep, 'emergency'));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $ep, 'radiology'));
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $ep, 'dentistry'));
|
|
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $dentist, 'dentistry'));
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $dentist, 'emergency'));
|
|
$this->assertFalse($this->modules->shouldShowSpecialtyNav($this->organization, $dentist));
|
|
|
|
$this->assertTrue($permissions->can($cashier, 'payments.manage'));
|
|
$this->assertFalse($permissions->can($cashier, 'bills.manage'));
|
|
$this->assertFalse($permissions->can($cashier, 'consultations.manage'));
|
|
$this->assertSame([], $this->modules->enabledModulesForMember($this->organization, $cashier));
|
|
|
|
$this->assertTrue($permissions->can($lab, 'lab.manage'));
|
|
$this->assertTrue($permissions->can($lab, 'pathology.result.enter'));
|
|
$this->assertFalse($permissions->can($lab, 'pathology.result.approve'));
|
|
$this->assertTrue($permissions->can($labMgr, 'pathology.result.approve'));
|
|
$this->assertTrue($permissions->can($labMgr, 'lab.catalog.manage'));
|
|
|
|
$this->assertTrue($permissions->can($billing, 'bills.manage'));
|
|
$this->assertFalse($permissions->can($billing, 'payments.manage'));
|
|
}
|
|
|
|
public function test_doctor_on_dentistry_sees_and_can_advance_stage(): void
|
|
{
|
|
[$doctorUser, $doctorMember, $visit] = $this->dentistryVisitForRole('dentist', 'den-doc');
|
|
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $doctorMember, 'dentistry'));
|
|
$this->assertTrue(app(\App\Services\Care\CarePermissions::class)->can($doctorMember, 'consultations.manage'));
|
|
|
|
$this->actingAs($doctorUser)
|
|
->get(route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'overview']))
|
|
->assertOk()
|
|
->assertSee('Seat at chair')
|
|
->assertSee('Start')
|
|
->assertDontSee('consultation access');
|
|
|
|
$this->actingAs($doctorUser)
|
|
->post(route('care.specialty.dentistry.stage', $visit), ['stage' => 'chair'])
|
|
->assertRedirect();
|
|
|
|
$this->assertSame('chair', $visit->fresh()->specialty_stage);
|
|
}
|
|
|
|
public function test_gp_with_consult_ability_but_no_module_manage_hides_mutate_ctas(): void
|
|
{
|
|
[$doctor, $member] = $this->makeStaff('general_physician', 'gp-no-manage');
|
|
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 No Manage',
|
|
'specialty' => 'General Practice',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$permissions = app(\App\Services\Care\CarePermissions::class);
|
|
$this->assertTrue($permissions->can($member, 'consultations.manage'));
|
|
$this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'psychiatry'));
|
|
$this->assertSame('refer', $this->modules->memberAccessLevel($this->organization, $member, 'psychiatry'));
|
|
|
|
$dept = Department::query()->where('type', 'psychiatry')->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-GP-NM',
|
|
'first_name' => 'Yaw',
|
|
'last_name' => 'ReferOnly',
|
|
'gender' => 'male',
|
|
'date_of_birth' => '1975-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(),
|
|
'waiting_at' => now(),
|
|
]);
|
|
|
|
$html = $this->actingAs($doctor)
|
|
->get(route('care.specialty.workspace', ['module' => 'psychiatry', 'visit' => $visit, 'tab' => 'overview']))
|
|
->assertOk()
|
|
->assertSee('view + refer')
|
|
->assertDontSee('consultation access')
|
|
->assertDontSee('Seat at chair')
|
|
->assertDontSee('Start triage')
|
|
->getContent();
|
|
|
|
$this->assertStringNotContainsString(
|
|
route('care.specialty.consultation.start', ['module' => 'psychiatry', 'visit' => $visit], absolute: false),
|
|
$html,
|
|
);
|
|
$this->assertStringNotContainsString('>Start</button>', $html);
|
|
}
|
|
|
|
public function test_admin_on_dentistry_sees_mutate_actions(): void
|
|
{
|
|
$dept = Department::query()->where('type', 'dental')->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-DEN-ADMIN',
|
|
'first_name' => 'Admin',
|
|
'last_name' => 'Patient',
|
|
'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(),
|
|
'specialty_stage' => 'waiting',
|
|
]);
|
|
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(),
|
|
'waiting_at' => now(),
|
|
]);
|
|
|
|
$this->actingAs($this->owner)
|
|
->get(route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'overview']))
|
|
->assertOk()
|
|
->assertSee('Seat at chair')
|
|
->assertSee('Start');
|
|
}
|
|
|
|
public function test_nurse_can_save_emergency_vitals_with_vitals_manage(): void
|
|
{
|
|
[$nurseUser, $nurseMember] = $this->makeStaff('ed_nurse', 'er-vitals');
|
|
$this->assertTrue(app(\App\Services\Care\CarePermissions::class)->can($nurseMember, 'vitals.manage'));
|
|
$this->assertFalse(app(\App\Services\Care\CarePermissions::class)->can($nurseMember, 'consultations.manage'));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $nurseMember, 'emergency'));
|
|
|
|
$dept = Department::query()->where('type', 'emergency')->firstOrFail();
|
|
$patient = Patient::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => $this->branch->id,
|
|
'patient_number' => 'P-ER-VITALS',
|
|
'first_name' => 'Kojo',
|
|
'last_name' => 'Mensah',
|
|
'gender' => 'male',
|
|
'date_of_birth' => '1988-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($nurseUser)
|
|
->get(route('care.specialty.workspace', ['module' => 'emergency', 'visit' => $visit, 'tab' => 'vitals']))
|
|
->assertOk()
|
|
->assertSee('Save vitals')
|
|
->assertDontSee('Seat at chair')
|
|
->assertDontSee('Start triage');
|
|
|
|
$this->actingAs($nurseUser)
|
|
->post(route('care.specialty.emergency.vitals', $visit), [
|
|
'bp_systolic' => 120,
|
|
'bp_diastolic' => 80,
|
|
'pulse' => 72,
|
|
])
|
|
->assertRedirect();
|
|
|
|
$this->actingAs($nurseUser)
|
|
->post(route('care.specialty.emergency.stage', $visit), ['stage' => 'treatment'])
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_doctor_with_prescriptions_manage_sees_prescribe_on_specialty_workspace(): void
|
|
{
|
|
[$doctorUser, $doctorMember, $visit] = $this->dentistryVisitForRole('dentist', 'den-rx');
|
|
$appointment = $visit->appointment()->firstOrFail();
|
|
$appointment->update([
|
|
'status' => Appointment::STATUS_IN_CONSULTATION,
|
|
'started_at' => now(),
|
|
'queue_ticket_status' => 'serving',
|
|
]);
|
|
$consultation = Consultation::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'visit_id' => $visit->id,
|
|
'appointment_id' => $appointment->id,
|
|
'patient_id' => $visit->patient_id,
|
|
'status' => Consultation::STATUS_DRAFT,
|
|
'started_at' => now(),
|
|
]);
|
|
|
|
$permissions = app(\App\Services\Care\CarePermissions::class);
|
|
$this->assertTrue($permissions->can($doctorMember, 'prescriptions.manage'));
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $doctorMember, 'dentistry'));
|
|
|
|
$html = $this->actingAs($doctorUser)
|
|
->get(route('care.specialty.workspace', [
|
|
'module' => 'dentistry',
|
|
'visit' => $visit,
|
|
'tab' => 'overview',
|
|
]))
|
|
->assertOk()
|
|
->assertSee('Prescribe')
|
|
->assertSee('New prescription')
|
|
->getContent();
|
|
|
|
$this->assertStringContainsString(
|
|
route('care.prescriptions.store', $consultation, absolute: false),
|
|
$html,
|
|
);
|
|
$this->assertStringContainsString('name="return_to" value="specialty"', $html);
|
|
$this->assertStringContainsString('name="specialty_module" value="dentistry"', $html);
|
|
}
|
|
|
|
public function test_nurse_without_prescriptions_manage_does_not_see_prescribe_cta(): void
|
|
{
|
|
[$nurseUser, $nurseMember, $visit] = $this->emergencyVisitForRole('ed_nurse', 'er-no-rx');
|
|
$appointment = $visit->appointment()->firstOrFail();
|
|
$appointment->update([
|
|
'status' => Appointment::STATUS_IN_CONSULTATION,
|
|
'started_at' => now(),
|
|
'queue_ticket_status' => 'serving',
|
|
]);
|
|
Consultation::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'visit_id' => $visit->id,
|
|
'appointment_id' => $appointment->id,
|
|
'patient_id' => $visit->patient_id,
|
|
'status' => Consultation::STATUS_DRAFT,
|
|
'started_at' => now(),
|
|
]);
|
|
|
|
$permissions = app(\App\Services\Care\CarePermissions::class);
|
|
$this->assertTrue($this->modules->memberCanManage($this->organization, $nurseMember, 'emergency'));
|
|
$this->assertFalse($permissions->can($nurseMember, 'prescriptions.manage'));
|
|
|
|
$this->actingAs($nurseUser)
|
|
->get(route('care.specialty.workspace', [
|
|
'module' => 'emergency',
|
|
'visit' => $visit,
|
|
'tab' => 'overview',
|
|
]))
|
|
->assertOk()
|
|
->assertDontSee('Prescribe')
|
|
->assertDontSee('New prescription');
|
|
}
|
|
|
|
public function test_specialty_prescribe_store_returns_to_workspace(): void
|
|
{
|
|
[$doctorUser, , $visit] = $this->dentistryVisitForRole('dentist', 'den-rx-store');
|
|
$appointment = $visit->appointment()->firstOrFail();
|
|
$appointment->update([
|
|
'status' => Appointment::STATUS_IN_CONSULTATION,
|
|
'started_at' => now(),
|
|
'queue_ticket_status' => 'serving',
|
|
]);
|
|
$consultation = Consultation::create([
|
|
'owner_ref' => $this->owner->public_id,
|
|
'visit_id' => $visit->id,
|
|
'appointment_id' => $appointment->id,
|
|
'patient_id' => $visit->patient_id,
|
|
'status' => Consultation::STATUS_DRAFT,
|
|
'started_at' => now(),
|
|
]);
|
|
|
|
$this->actingAs($doctorUser)
|
|
->post(route('care.prescriptions.store', $consultation), [
|
|
'activate' => true,
|
|
'return_to' => 'specialty',
|
|
'specialty_module' => 'dentistry',
|
|
'specialty_visit_id' => $visit->id,
|
|
'from_consultation' => $consultation->uuid,
|
|
'items' => [
|
|
[
|
|
'name' => 'Amoxicillin 500mg',
|
|
'dosage' => '1 capsule',
|
|
'frequency' => 'TDS',
|
|
'duration' => '5 days',
|
|
'route' => 'oral',
|
|
],
|
|
],
|
|
])
|
|
->assertRedirect(route('care.specialty.workspace', [
|
|
'module' => 'dentistry',
|
|
'visit' => $visit,
|
|
]));
|
|
|
|
$this->assertDatabaseHas('care_prescriptions', [
|
|
'consultation_id' => $consultation->id,
|
|
'visit_id' => $visit->id,
|
|
'patient_id' => $visit->patient_id,
|
|
]);
|
|
}
|
|
}
|