From 56a663a777704ad01e09568671baf10e93286a48 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Mon, 20 Jul 2026 09:48:19 +0000 Subject: [PATCH] Replace Maternity with Women's Health (OB/GYN) and configurable service lines. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keeps Fertility as a separate specialty, adds OB/GYN and fertility staff roles, and migrates live org settings from maternity → womens_health. Co-authored-by: Cursor --- .../Care/SettingsModulesController.php | 33 ++++ .../Care/SpecialtyModuleController.php | 33 ++-- ...hp => WomensHealthWorkspaceController.php} | 72 ++++---- app/Services/Care/CarePermissions.php | 91 ++++++++-- app/Services/Care/DemoTenantSeeder.php | 12 +- .../Care/SpecialtyClinicalRecordService.php | 14 +- app/Services/Care/SpecialtyModuleService.php | 159 ++++++++++++++++++ app/Services/Care/SpecialtyShellService.php | 49 ++++-- .../Care/SpecialtyVisitStageService.php | 6 +- .../WomensHealthAnalyticsService.php} | 14 +- .../WomensHealthWorkflowService.php} | 6 +- app/Support/DemoWorld.php | 2 +- config/care.php | 17 +- config/care_specialty_clinical.php | 4 +- config/care_specialty_modules.php | 60 ++++--- config/care_specialty_shell.php | 21 ++- config/care_workflows.php | 4 +- resources/views/care/settings/edit.blade.php | 2 +- .../views/care/settings/module-show.blade.php | 31 ++++ .../specialty/partials/actions-menu.blade.php | 4 +- .../specialty/sections/workspace.blade.php | 4 +- .../views/care/specialty/shell.blade.php | 4 +- .../print.blade.php | 4 +- .../reports.blade.php | 12 +- .../workspace-overview.blade.php | 6 +- .../workspace-postnatal.blade.php | 4 +- routes/web.php | 12 +- tests/Feature/CareSpecialtyAccessTest.php | 6 +- tests/Feature/CareSpecialtyModulesTest.php | 2 +- tests/Feature/CareSpecialtyShellTest.php | 4 +- tests/Feature/CareWomensHealthAccessTest.php | 157 +++++++++++++++++ ...Test.php => CareWomensHealthSuiteTest.php} | 44 ++--- tests/Feature/DemoSeedCommandTest.php | 4 +- 33 files changed, 708 insertions(+), 189 deletions(-) rename app/Http/Controllers/Care/{MaternityWorkspaceController.php => WomensHealthWorkspaceController.php} (71%) rename app/Services/Care/{Maternity/MaternityAnalyticsService.php => WomensHealth/WomensHealthAnalyticsService.php} (95%) rename app/Services/Care/{Maternity/MaternityWorkflowService.php => WomensHealth/WomensHealthWorkflowService.php} (95%) rename resources/views/care/specialty/{maternity => womens_health}/print.blade.php (96%) rename resources/views/care/specialty/{maternity => womens_health}/reports.blade.php (91%) rename resources/views/care/specialty/{maternity => womens_health}/workspace-overview.blade.php (90%) rename resources/views/care/specialty/{maternity => womens_health}/workspace-postnatal.blade.php (92%) create mode 100644 tests/Feature/CareWomensHealthAccessTest.php rename tests/Feature/{CareMaternitySuiteTest.php => CareWomensHealthSuiteTest.php} (86%) diff --git a/app/Http/Controllers/Care/SettingsModulesController.php b/app/Http/Controllers/Care/SettingsModulesController.php index b6de031..abc8437 100644 --- a/app/Http/Controllers/Care/SettingsModulesController.php +++ b/app/Http/Controllers/Care/SettingsModulesController.php @@ -47,6 +47,7 @@ class SettingsModulesController extends Controller CarePricingService $pricing, ): View { $this->authorizeAbility($request, 'settings.view'); + $module = $modules->normalizeKey($module); $definition = $modules->definition($module); abort_unless($definition, 404); @@ -69,6 +70,8 @@ class SettingsModulesController extends Controller 'moduleKey' => $module, 'definition' => $definition, 'services' => $services, + 'serviceLines' => $modules->serviceLines($module), + 'enabledServiceLines' => $modules->enabledServiceLines($organization, $module), 'enabled' => $modules->isEnabled($organization, $module), 'canManage' => $canManage && $canUse, 'canUseSpecialtyModules' => $canUse, @@ -77,6 +80,36 @@ class SettingsModulesController extends Controller ]); } + public function updateServiceLines( + Request $request, + string $module, + SpecialtyModuleService $modules, + ): RedirectResponse { + $this->authorizeAbility($request, 'settings.manage'); + $organization = $this->organization($request); + abort_unless($modules->definition($module), 404); + abort_unless($modules->canManage($organization), 403); + abort_unless($modules->serviceLines($module) !== [], 404); + + $lines = $modules->serviceLines($module); + $rules = ['lines' => ['required', 'array']]; + foreach (array_keys($lines) as $key) { + $rules["lines.{$key}"] = ['nullable', 'boolean']; + } + $validated = $request->validate($rules); + + $desired = []; + foreach (array_keys($lines) as $key) { + $desired[$key] = (bool) ($validated['lines'][$key] ?? false); + } + + $modules->syncServiceLines($organization, $module, $desired); + + return redirect() + ->route('care.settings.modules.show', $module) + ->with('success', 'Service lines saved.'); + } + public function updateServices( Request $request, string $module, diff --git a/app/Http/Controllers/Care/SpecialtyModuleController.php b/app/Http/Controllers/Care/SpecialtyModuleController.php index 852f784..131d7fe 100644 --- a/app/Http/Controllers/Care/SpecialtyModuleController.php +++ b/app/Http/Controllers/Care/SpecialtyModuleController.php @@ -155,7 +155,7 @@ class SpecialtyModuleController extends Controller 'blood_bank' => 'requests', 'ophthalmology' => 'refraction', 'physiotherapy' => 'assessment', - 'maternity' => 'history', + 'womens_health' => 'history', 'radiology' => 'protocol', 'cardiology' => 'history', 'psychiatry' => 'history', @@ -246,7 +246,7 @@ class SpecialtyModuleController extends Controller 'blood_bank' => 'requests', 'ophthalmology' => 'refraction', 'physiotherapy' => 'assessment', - 'maternity' => 'history', + 'womens_health' => 'history', 'radiology' => 'protocol', 'cardiology' => 'history', 'psychiatry' => 'history', @@ -495,14 +495,14 @@ class SpecialtyModuleController extends Controller } } - if ($module === 'maternity' && in_array($recordType, ['anc_history', 'obstetric_exam', 'fetal_notes', 'mat_investigation', 'mat_plan'], true)) { - $workflow = app(\App\Services\Care\Maternity\MaternityWorkflowService::class); + if ($module === 'womens_health' && in_array($recordType, ['anc_history', 'obstetric_exam', 'fetal_notes', 'mat_investigation', 'mat_plan'], true)) { + $workflow = app(\App\Services\Care\WomensHealth\WomensHealthWorkflowService::class); $stageService = app(\App\Services\Care\SpecialtyVisitStageService::class); $suggested = match ($recordType) { 'anc_history' => $workflow->stageFromHistory($record->payload ?? []), 'obstetric_exam' => $workflow->stageFromExam($record->payload ?? []), 'fetal_notes' => $workflow->stageFromFetal($record->payload ?? []), - 'mat_investigation' => \App\Services\Care\Maternity\MaternityWorkflowService::STAGE_INVESTIGATION, + 'mat_investigation' => \App\Services\Care\WomensHealth\WomensHealthWorkflowService::STAGE_INVESTIGATION, 'mat_plan' => $workflow->stageFromPlan($record->payload ?? []), default => null, }; @@ -522,7 +522,7 @@ class SpecialtyModuleController extends Controller $stageService->setStage( $organization, $visit, - 'maternity', + 'womens_health', $suggested, $this->ownerRef($request), $this->ownerRef($request), @@ -534,7 +534,7 @@ class SpecialtyModuleController extends Controller $stageService->setStage( $organization, $visit, - 'maternity', + 'womens_health', $suggested, $this->ownerRef($request), $this->ownerRef($request), @@ -1424,6 +1424,7 @@ class SpecialtyModuleController extends Controller CareQueueBridge $queueBridge, ?Visit $visit = null, ): View { + $module = $modules->normalizeKey($module); $definition = $modules->definition($module); abort_unless($definition, 404); @@ -1849,15 +1850,15 @@ class SpecialtyModuleController extends Controller $physiotherapyStageFlow = app(\App\Services\Care\Physiotherapy\PhysiotherapyWorkflowService::class)->stageFlow(); } - if ($module === 'maternity') { - $maternityHistory = $clinical->findForVisit($workspaceVisit, 'maternity', 'anc_history'); - $maternityExam = $clinical->findForVisit($workspaceVisit, 'maternity', 'obstetric_exam'); - $maternityFetal = $clinical->findForVisit($workspaceVisit, 'maternity', 'fetal_notes'); - $maternityInvestigation = $clinical->findForVisit($workspaceVisit, 'maternity', 'mat_investigation'); - $maternityPlan = $clinical->findForVisit($workspaceVisit, 'maternity', 'mat_plan'); - $maternityPostnatal = $clinical->findForVisit($workspaceVisit, 'maternity', 'postnatal_note'); - $maternityStageCodes = collect($shell->stages('maternity'))->pluck('code')->all(); - $maternityStageFlow = app(\App\Services\Care\Maternity\MaternityWorkflowService::class)->stageFlow(); + if ($module === 'womens_health') { + $maternityHistory = $clinical->findForVisit($workspaceVisit, 'womens_health', 'anc_history'); + $maternityExam = $clinical->findForVisit($workspaceVisit, 'womens_health', 'obstetric_exam'); + $maternityFetal = $clinical->findForVisit($workspaceVisit, 'womens_health', 'fetal_notes'); + $maternityInvestigation = $clinical->findForVisit($workspaceVisit, 'womens_health', 'mat_investigation'); + $maternityPlan = $clinical->findForVisit($workspaceVisit, 'womens_health', 'mat_plan'); + $maternityPostnatal = $clinical->findForVisit($workspaceVisit, 'womens_health', 'postnatal_note'); + $maternityStageCodes = collect($shell->stages('womens_health'))->pluck('code')->all(); + $maternityStageFlow = app(\App\Services\Care\WomensHealth\WomensHealthWorkflowService::class)->stageFlow(); } if ($module === 'radiology') { diff --git a/app/Http/Controllers/Care/MaternityWorkspaceController.php b/app/Http/Controllers/Care/WomensHealthWorkspaceController.php similarity index 71% rename from app/Http/Controllers/Care/MaternityWorkspaceController.php rename to app/Http/Controllers/Care/WomensHealthWorkspaceController.php index bb97e98..1d8dea0 100644 --- a/app/Http/Controllers/Care/MaternityWorkspaceController.php +++ b/app/Http/Controllers/Care/WomensHealthWorkspaceController.php @@ -6,8 +6,8 @@ use App\Http\Controllers\Care\Concerns\ScopesToAccount; use App\Http\Controllers\Controller; use App\Models\SpecialtyClinicalRecord; use App\Models\Visit; -use App\Services\Care\Maternity\MaternityAnalyticsService; -use App\Services\Care\Maternity\MaternityWorkflowService; +use App\Services\Care\WomensHealth\WomensHealthAnalyticsService; +use App\Services\Care\WomensHealth\WomensHealthWorkflowService; use App\Services\Care\SpecialtyClinicalRecordService; use App\Services\Care\SpecialtyModuleService; use App\Services\Care\SpecialtyShellService; @@ -16,20 +16,20 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; -class MaternityWorkspaceController extends Controller +class WomensHealthWorkspaceController extends Controller { use ScopesToAccount; - protected function assertMaternityAccess(Request $request, SpecialtyModuleService $modules): void + protected function assertWomensHealthAccess(Request $request, SpecialtyModuleService $modules): void { $organization = $this->organization($request); - abort_unless($modules->memberCanAccess($organization, $this->member($request), 'maternity'), 403); + abort_unless($modules->memberCanAccess($organization, $this->member($request), 'womens_health'), 403); } - protected function assertMaternityManage(Request $request, SpecialtyModuleService $modules): void + protected function assertWomensHealthManage(Request $request, SpecialtyModuleService $modules): void { $organization = $this->organization($request); - abort_unless($modules->memberCanManage($organization, $this->member($request), 'maternity'), 403); + abort_unless($modules->memberCanManage($organization, $this->member($request), 'womens_health'), 403); } protected function assertVisit(Request $request, Visit $visit): void @@ -46,7 +46,7 @@ class MaternityWorkspaceController extends Controller SpecialtyShellService $shell, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); - $this->assertMaternityManage($request, $modules); + $this->assertWomensHealthManage($request, $modules); $this->assertVisit($request, $visit); $validated = $request->validate([ @@ -57,7 +57,7 @@ class MaternityWorkspaceController extends Controller $stages->setStage( $this->organization($request), $visit, - 'maternity', + 'womens_health', $validated['stage'], $this->ownerRef($request), $this->actorRef($request), @@ -68,9 +68,9 @@ class MaternityWorkspaceController extends Controller return redirect() ->route('care.specialty.workspace', [ - 'module' => 'maternity', + 'module' => 'womens_health', 'visit' => $visit, - 'tab' => $shell->workspaceTabForStage('maternity', $validated['stage']), + 'tab' => $shell->workspaceTabForStage('womens_health', $validated['stage']), ]) ->with('success', 'Visit stage updated.'); } @@ -81,17 +81,17 @@ class MaternityWorkspaceController extends Controller SpecialtyModuleService $modules, SpecialtyClinicalRecordService $clinical, SpecialtyVisitStageService $stages, - MaternityWorkflowService $workflow, + WomensHealthWorkflowService $workflow, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); - $this->assertMaternityManage($request, $modules); + $this->assertWomensHealthManage($request, $modules); $this->assertVisit($request, $visit); $organization = $this->organization($request); $owner = $this->ownerRef($request); $payload = (array) $request->input('payload', []); - foreach ($clinical->fieldsFor('maternity', 'postnatal_note') as $field) { + foreach ($clinical->fieldsFor('womens_health', 'postnatal_note') as $field) { if (($field['type'] ?? '') === 'boolean') { $payload[$field['name']] = $request->boolean('payload.'.$field['name']); } @@ -100,17 +100,17 @@ class MaternityWorkspaceController extends Controller $validated = $request->validate(array_merge([ 'tab' => ['required', 'string'], - ], $clinical->validationRules('maternity', 'postnatal_note'))); + ], $clinical->validationRules('womens_health', 'postnatal_note'))); $record = $clinical->upsert( $organization, $visit, - 'maternity', + 'womens_health', 'postnatal_note', $clinical->payloadFromRequest($validated), $owner, $owner, - MaternityWorkflowService::STAGE_POSTNATAL, + WomensHealthWorkflowService::STAGE_POSTNATAL, SpecialtyClinicalRecord::STATUS_COMPLETED, ); @@ -118,8 +118,8 @@ class MaternityWorkspaceController extends Controller $stages->setStage( $organization, $visit, - 'maternity', - MaternityWorkflowService::STAGE_POSTNATAL, + 'womens_health', + WomensHealthWorkflowService::STAGE_POSTNATAL, $owner, $owner, ); @@ -131,8 +131,8 @@ class MaternityWorkspaceController extends Controller $stages->setStage( $organization, $visit, - 'maternity', - MaternityWorkflowService::STAGE_COMPLETED, + 'womens_health', + WomensHealthWorkflowService::STAGE_COMPLETED, $owner, $owner, ); @@ -157,7 +157,7 @@ class MaternityWorkspaceController extends Controller } return redirect() - ->route('care.specialty.workspace', ['module' => 'maternity', 'visit' => $visit, 'tab' => 'postnatal']) + ->route('care.specialty.workspace', ['module' => 'womens_health', 'visit' => $visit, 'tab' => 'postnatal']) ->with('success', 'Delivery / postnatal note saved.'); } @@ -165,10 +165,10 @@ class MaternityWorkspaceController extends Controller Request $request, SpecialtyModuleService $modules, SpecialtyShellService $shell, - MaternityAnalyticsService $analytics, + WomensHealthAnalyticsService $analytics, ): View { $this->authorizeAbility($request, 'consultations.view'); - $this->assertMaternityAccess($request, $modules); + $this->assertWomensHealthAccess($request, $modules); $organization = $this->organization($request); $branchScope = app(\App\Services\Care\OrganizationResolver::class)->branchScope($this->member($request)); @@ -181,10 +181,10 @@ class MaternityWorkspaceController extends Controller $request->query('to'), ); - return view('care.specialty.maternity.reports', [ - 'moduleKey' => 'maternity', - 'definition' => $modules->definition('maternity'), - 'shellNav' => $shell->navItems('maternity'), + return view('care.specialty.womens_health.reports', [ + 'moduleKey' => 'womens_health', + 'definition' => $modules->definition('womens_health'), + 'shellNav' => $shell->navItems('womens_health'), 'report' => $report, 'currency' => strtoupper((string) config('care.billing.currency', config('care.currency', 'GHS'))), ]); @@ -197,20 +197,20 @@ class MaternityWorkspaceController extends Controller SpecialtyClinicalRecordService $clinical, ): View { $this->authorizeAbility($request, 'consultations.view'); - $this->assertMaternityAccess($request, $modules); + $this->assertWomensHealthAccess($request, $modules); $this->assertVisit($request, $visit); $visit->loadMissing(['patient', 'appointment.practitioner', 'branch']); - return view('care.specialty.maternity.print', [ + return view('care.specialty.womens_health.print', [ 'visit' => $visit, 'patient' => $visit->patient, - 'history' => $clinical->findForVisit($visit, 'maternity', 'anc_history'), - 'exam' => $clinical->findForVisit($visit, 'maternity', 'obstetric_exam'), - 'fetal' => $clinical->findForVisit($visit, 'maternity', 'fetal_notes'), - 'investigation' => $clinical->findForVisit($visit, 'maternity', 'mat_investigation'), - 'plan' => $clinical->findForVisit($visit, 'maternity', 'mat_plan'), - 'postnatal' => $clinical->findForVisit($visit, 'maternity', 'postnatal_note'), + 'history' => $clinical->findForVisit($visit, 'womens_health', 'anc_history'), + 'exam' => $clinical->findForVisit($visit, 'womens_health', 'obstetric_exam'), + 'fetal' => $clinical->findForVisit($visit, 'womens_health', 'fetal_notes'), + 'investigation' => $clinical->findForVisit($visit, 'womens_health', 'mat_investigation'), + 'plan' => $clinical->findForVisit($visit, 'womens_health', 'mat_plan'), + 'postnatal' => $clinical->findForVisit($visit, 'womens_health', 'postnatal_note'), ]); } } diff --git a/app/Services/Care/CarePermissions.php b/app/Services/Care/CarePermissions.php index 4c59660..c18ef84 100644 --- a/app/Services/Care/CarePermissions.php +++ b/app/Services/Care/CarePermissions.php @@ -60,7 +60,11 @@ class CarePermissions 'dentist' => ['dentistry'], 'psychiatrist' => ['psychiatry'], 'physiotherapist' => ['physiotherapy'], - 'midwife' => ['maternity'], + 'midwife' => ['womens_health'], + 'obstetrician' => ['womens_health'], + 'gynecologist' => ['womens_health'], + 'obgyn' => ['womens_health'], + 'labour_ward_nurse' => ['womens_health'], 'pediatrician' => ['pediatrics'], 'oncologist' => ['oncology', 'infusion'], 'cardiologist' => ['cardiology'], @@ -70,6 +74,8 @@ class CarePermissions 'orthopedist' => ['orthopedics'], 'podiatrist' => ['podiatry'], 'fertility_specialist' => ['fertility'], + 'embryologist' => ['fertility'], + 'fertility_nurse' => ['fertility'], 'dialysis_nurse' => ['renal'], 'ambulance_staff' => ['ambulance', 'emergency'], 'receptionist' => [], // refer only — see roleReferApps @@ -77,7 +83,7 @@ class CarePermissions 'cashier' => [], 'accountant' => [], 'department_manager' => null, // department analytics — all enabled modules view - 'nurse' => ['vaccination', 'child_welfare', 'infusion', 'maternity'], + 'nurse' => ['vaccination', 'child_welfare', 'infusion', 'womens_health'], ]; /** @@ -88,21 +94,25 @@ class CarePermissions */ protected array $roleReferApps = [ 'general_physician' => [ - 'dentistry', 'physiotherapy', 'maternity', 'psychiatry', 'orthopedics', + 'dentistry', 'physiotherapy', 'womens_health', 'psychiatry', 'orthopedics', 'oncology', 'renal', 'surgery', 'radiology', 'pathology', 'blood_bank', 'infusion', 'podiatry', 'fertility', 'ambulance', ], 'doctor' => [ - 'dentistry', 'physiotherapy', 'maternity', 'psychiatry', 'orthopedics', + 'dentistry', 'physiotherapy', 'womens_health', 'psychiatry', 'orthopedics', 'oncology', 'renal', 'surgery', 'radiology', 'pathology', 'blood_bank', 'infusion', 'podiatry', 'fertility', 'ambulance', ], 'nurse' => ['emergency', 'pediatrics', 'blood_bank'], - 'pharmacist' => ['oncology', 'infusion', 'maternity', 'emergency'], + 'pharmacist' => ['oncology', 'infusion', 'womens_health', 'emergency'], 'receptionist' => [ 'emergency', 'pediatrics', 'vaccination', 'child_welfare', 'dentistry', - 'maternity', 'ambulance', 'ophthalmology', 'physiotherapy', + 'womens_health', 'ambulance', 'ophthalmology', 'physiotherapy', ], + 'obstetrician' => ['fertility'], + 'gynecologist' => ['fertility'], + 'obgyn' => ['fertility'], + 'midwife' => ['fertility'], ]; /** @@ -115,7 +125,7 @@ class CarePermissions 'lab_technician' => ['radiology'], 'lab_manager' => ['radiology'], 'pathologist' => ['blood_bank'], - 'pharmacist' => ['oncology', 'infusion', 'maternity', 'emergency'], + 'pharmacist' => ['oncology', 'infusion', 'womens_health', 'emergency'], ]; /** @@ -284,7 +294,45 @@ class CarePermissions 'consultations.view', 'consultations.manage', 'vitals.manage', 'assessments.view', 'assessments.capture', 'assessments.manage', - 'maternity.view', 'maternity.manage', + 'womens_health.view', 'womens_health.manage', + 'service_queues.console', + ], + 'obstetrician' => [ + 'dashboard.view', 'patients.view', 'appointments.view', + 'consultations.view', 'consultations.manage', + 'investigations.request', 'prescriptions.manage', + 'vitals.manage', + 'assessments.view', 'assessments.capture', 'assessments.manage', + 'pathways.manage', + 'womens_health.view', 'womens_health.manage', + 'service_queues.console', + ], + 'gynecologist' => [ + 'dashboard.view', 'patients.view', 'appointments.view', + 'consultations.view', 'consultations.manage', + 'investigations.request', 'prescriptions.manage', + 'vitals.manage', + 'assessments.view', 'assessments.capture', 'assessments.manage', + 'pathways.manage', + 'womens_health.view', 'womens_health.manage', + 'service_queues.console', + ], + 'obgyn' => [ + 'dashboard.view', 'patients.view', 'appointments.view', + 'consultations.view', 'consultations.manage', + 'investigations.request', 'prescriptions.manage', + 'vitals.manage', + 'assessments.view', 'assessments.capture', 'assessments.manage', + 'pathways.manage', + 'womens_health.view', 'womens_health.manage', + 'service_queues.console', + ], + 'labour_ward_nurse' => [ + 'dashboard.view', 'patients.view', 'appointments.view', + 'consultations.view', + 'vitals.manage', + 'assessments.view', 'assessments.capture', + 'womens_health.view', 'womens_health.manage', 'service_queues.console', ], 'pediatrician' => [ @@ -378,6 +426,20 @@ class CarePermissions 'fertility.view', 'fertility.manage', 'service_queues.console', ], + 'embryologist' => [ + 'dashboard.view', 'patients.view', 'appointments.view', + 'consultations.view', + 'fertility.view', 'fertility.manage', + 'service_queues.console', + ], + 'fertility_nurse' => [ + 'dashboard.view', 'patients.view', 'appointments.view', + 'consultations.view', + 'vitals.manage', + 'assessments.view', 'assessments.capture', + 'fertility.view', 'fertility.manage', + 'service_queues.console', + ], 'dialysis_nurse' => [ 'dashboard.view', 'patients.view', 'appointments.view', 'consultations.view', @@ -443,9 +505,10 @@ class CarePermissions 'lab_technician', 'lab_manager', 'pathologist', 'blood_bank_officer', 'blood_bank_manager', 'pharmacist', 'dentist', 'psychiatrist', 'physiotherapist', - 'midwife', 'pediatrician', 'oncologist', + 'midwife', 'obstetrician', 'gynecologist', 'obgyn', 'labour_ward_nurse', + 'pediatrician', 'oncologist', 'cardiologist', 'ent_specialist', 'dermatologist', 'ophthalmologist', - 'orthopedist', 'podiatrist', 'fertility_specialist', + 'orthopedist', 'podiatrist', 'fertility_specialist', 'embryologist', 'fertility_nurse', 'dialysis_nurse', 'ambulance_staff', 'receptionist', 'cashier', 'billing_officer', 'nurse', ]; @@ -454,9 +517,10 @@ class CarePermissions protected array $clinicalPractitionerRoles = [ 'emergency_physician', 'general_physician', 'doctor', 'surgeon', 'radiologist', 'pathologist', 'dentist', 'psychiatrist', - 'physiotherapist', 'midwife', 'pediatrician', 'oncologist', + 'physiotherapist', 'midwife', 'obstetrician', 'gynecologist', 'obgyn', + 'labour_ward_nurse', 'pediatrician', 'oncologist', 'cardiologist', 'ent_specialist', 'dermatologist', 'ophthalmologist', - 'orthopedist', 'podiatrist', 'fertility_specialist', + 'orthopedist', 'podiatrist', 'fertility_specialist', 'embryologist', 'fertility_nurse', 'ed_nurse', 'theatre_nurse', 'dialysis_nurse', 'ambulance_staff', 'radiographer', 'lab_technician', 'lab_manager', 'blood_bank_officer', 'blood_bank_manager', 'pharmacist', 'nurse', @@ -650,8 +714,9 @@ class CarePermissions 'emergency_physician', 'general_physician', 'doctor', 'surgeon', 'radiologist', 'pathologist', 'dentist', 'psychiatrist', 'pediatrician', 'oncologist', 'physiotherapist', 'midwife', + 'obstetrician', 'gynecologist', 'obgyn', 'fertility_specialist', 'cardiologist', 'ent_specialist', 'dermatologist', 'ophthalmologist', - 'orthopedist', 'podiatrist', 'fertility_specialist', + 'orthopedist', 'podiatrist', ], true); } diff --git a/app/Services/Care/DemoTenantSeeder.php b/app/Services/Care/DemoTenantSeeder.php index f3c7a6b..5b23199 100644 --- a/app/Services/Care/DemoTenantSeeder.php +++ b/app/Services/Care/DemoTenantSeeder.php @@ -866,7 +866,7 @@ class DemoTenantSeeder 'dentistry' => ['Toothache', 'Dental cleaning', 'Extraction review'], 'ophthalmology' => ['Blurry vision', 'Eye exam', 'Contact lens fitting'], 'physiotherapy' => ['Back pain rehab', 'Post-injury physio', 'Mobility session'], - 'maternity' => ['Antenatal visit', 'Pregnancy check', 'Postnatal review'], + 'womens_health' => ['Antenatal visit', 'Pregnancy check', 'Postnatal review'], 'radiology' => ['X-ray referral', 'Ultrasound', 'Imaging review'], 'cardiology' => ['Chest pain review', 'ECG follow-up', 'Hypertension clinic'], 'psychiatry' => ['Mental health review', 'Counselling session', 'Medication review'], @@ -1400,7 +1400,7 @@ class DemoTenantSeeder $departmentIds = Department::owned($ownerRef) ->whereIn('branch_id', $branchIds) - ->where('type', 'maternity') + ->where('type', 'womens_health') ->where('is_active', true) ->pluck('id'); @@ -1435,7 +1435,7 @@ class DemoTenantSeeder $clinical->upsert( $organization, $visit, - 'maternity', + 'womens_health', 'anc_history', [ 'gravida' => $highRisk ? 3 : 1, @@ -1458,7 +1458,7 @@ class DemoTenantSeeder $clinical->upsert( $organization, $visit, - 'maternity', + 'womens_health', 'obstetric_exam', [ 'bp' => $highRisk ? '158/102' : '118/74', @@ -1483,7 +1483,7 @@ class DemoTenantSeeder $clinical->upsert( $organization, $visit, - 'maternity', + 'womens_health', 'fetal_notes', [ 'fetal_heart_rate' => $highRisk ? 168 : 142, @@ -1502,7 +1502,7 @@ class DemoTenantSeeder $clinical->upsert( $organization, $visit, - 'maternity', + 'womens_health', 'mat_plan', [ 'risk_category' => 'High risk', diff --git a/app/Services/Care/SpecialtyClinicalRecordService.php b/app/Services/Care/SpecialtyClinicalRecordService.php index 657d06e..5175043 100644 --- a/app/Services/Care/SpecialtyClinicalRecordService.php +++ b/app/Services/Care/SpecialtyClinicalRecordService.php @@ -53,10 +53,16 @@ class SpecialtyClinicalRecordService public function findForVisit(Visit $visit, string $moduleKey, string $recordType): ?SpecialtyClinicalRecord { + $keys = [$moduleKey]; + if ($moduleKey === 'womens_health') { + $keys[] = 'maternity'; // legacy clinical rows + } + return SpecialtyClinicalRecord::owned($visit->owner_ref) ->where('visit_id', $visit->id) - ->where('module_key', $moduleKey) + ->whereIn('module_key', $keys) ->where('record_type', $recordType) + ->orderByDesc('id') ->first(); } @@ -314,7 +320,7 @@ class SpecialtyClinicalRecordService } } - if ($moduleKey === 'maternity' && $recordType === 'obstetric_exam') { + if ($moduleKey === 'womens_health' && $recordType === 'obstetric_exam') { $danger = trim((string) ($payload['danger_signs'] ?? '')); if ($danger !== '') { $alerts[] = [ @@ -343,7 +349,7 @@ class SpecialtyClinicalRecordService } } - if ($moduleKey === 'maternity' && $recordType === 'fetal_notes') { + if ($moduleKey === 'womens_health' && $recordType === 'fetal_notes') { $fhr = isset($payload['fetal_heart_rate']) ? (int) $payload['fetal_heart_rate'] : null; if ($fhr !== null && ($fhr < 110 || $fhr > 160)) { $alerts[] = [ @@ -362,7 +368,7 @@ class SpecialtyClinicalRecordService } } - if ($moduleKey === 'maternity' && $recordType === 'mat_plan') { + if ($moduleKey === 'womens_health' && $recordType === 'mat_plan') { $risk = strtolower((string) ($payload['risk_category'] ?? '')); if (str_contains($risk, 'high')) { $alerts[] = [ diff --git a/app/Services/Care/SpecialtyModuleService.php b/app/Services/Care/SpecialtyModuleService.php index 24a30b5..146a75c 100644 --- a/app/Services/Care/SpecialtyModuleService.php +++ b/app/Services/Care/SpecialtyModuleService.php @@ -45,13 +45,160 @@ class SpecialtyModuleService return config('care.specialty_modules', []); } + /** + * Legacy maternity module key → womens_health. + */ + public function normalizeKey(string $key): string + { + return $key === 'maternity' ? 'womens_health' : $key; + } + public function definition(string $key): ?array { + $key = $this->normalizeKey($key); $catalog = $this->catalog(); return $catalog[$key] ?? null; } + /** + * Migrate org settings from maternity → womens_health (idempotent). + */ + public function migrateMaternityToWomensHealth(Organization $organization): Organization + { + $settings = is_array($organization->settings) ? $organization->settings : []; + $modules = is_array($settings['specialty_modules'] ?? null) ? $settings['specialty_modules'] : []; + $provisioning = is_array($settings['specialty_module_provisioning'] ?? null) + ? $settings['specialty_module_provisioning'] + : []; + + $changed = false; + if (! empty($modules['maternity']) && empty($modules['womens_health'])) { + $modules['womens_health'] = true; + $modules['maternity'] = false; + $changed = true; + } + if (isset($provisioning['maternity']) && ! isset($provisioning['womens_health'])) { + $provisioning['womens_health'] = $provisioning['maternity']; + unset($provisioning['maternity']); + $changed = true; + } elseif (isset($provisioning['maternity']) && isset($provisioning['womens_health'])) { + unset($provisioning['maternity']); + $changed = true; + } + + if (! $changed) { + return $organization; + } + + $settings['specialty_modules'] = $modules; + $settings['specialty_module_provisioning'] = $provisioning; + $organization->update(['settings' => $settings]); + + // Point legacy maternity departments at the new type when still labeled maternity. + Department::query() + ->where('organization_id', $organization->id) + ->where('type', 'maternity') + ->update(['type' => 'womens_health']); + + return $organization->fresh(); + } + + /** + * @return array + */ + public function serviceLines(string $moduleKey): array + { + $definition = $this->definition($moduleKey); + $lines = $definition['service_lines'] ?? []; + if (! is_array($lines)) { + return []; + } + + $out = []; + foreach ($lines as $key => $line) { + if (! is_string($key) || ! is_array($line)) { + continue; + } + $out[$key] = [ + 'label' => (string) ($line['label'] ?? $key), + 'default_on' => (bool) ($line['default_on'] ?? true), + ]; + } + + return $out; + } + + /** + * Enabled service lines for an org module (defaults when unset). + * + * @return array + */ + public function enabledServiceLines(Organization $organization, string $moduleKey): array + { + $moduleKey = $this->normalizeKey($moduleKey); + $catalog = $this->serviceLines($moduleKey); + if ($catalog === []) { + return []; + } + + $stored = data_get( + $organization->settings, + "specialty_module_provisioning.{$moduleKey}.service_lines", + [], + ); + $stored = is_array($stored) ? $stored : []; + + $out = []; + foreach ($catalog as $key => $line) { + $out[$key] = array_key_exists($key, $stored) + ? (bool) $stored[$key] + : $line['default_on']; + } + + return $out; + } + + /** + * @param array $desired + */ + public function syncServiceLines(Organization $organization, string $moduleKey, array $desired): void + { + $moduleKey = $this->normalizeKey($moduleKey); + $catalog = $this->serviceLines($moduleKey); + abort_unless($catalog !== [], 404); + + $settings = is_array($organization->settings) ? $organization->settings : []; + $provisioning = is_array($settings['specialty_module_provisioning'] ?? null) + ? $settings['specialty_module_provisioning'] + : []; + $record = is_array($provisioning[$moduleKey] ?? null) ? $provisioning[$moduleKey] : []; + + $lines = []; + foreach (array_keys($catalog) as $key) { + $lines[$key] = (bool) ($desired[$key] ?? false); + } + // Keep at least one line on so the module remains usable. + if (! in_array(true, $lines, true)) { + $first = array_key_first($catalog); + if ($first !== null) { + $lines[$first] = true; + } + } + + $record['service_lines'] = $lines; + $provisioning[$moduleKey] = $record; + $settings['specialty_module_provisioning'] = $provisioning; + $organization->update(['settings' => $settings]); + } + + public function isServiceLineEnabled(Organization $organization, string $moduleKey, string $lineKey): bool + { + $enabled = $this->enabledServiceLines($organization, $moduleKey); + + return (bool) ($enabled[$lineKey] ?? false); + } + /** * Heroicon-style icon identifier from the specialty catalog (e.g. bolt, heart). */ @@ -86,10 +233,18 @@ class SpecialtyModuleService public function isEnabled(Organization $organization, string $key): bool { + $key = $this->normalizeKey($key); + if (! $this->definition($key)) { return false; } + // One-time maternity → womens_health settings migration. + if ($key === 'womens_health' + && data_get($organization->settings, 'specialty_modules.maternity')) { + $organization = $this->migrateMaternityToWomensHealth($organization); + } + if ($this->isDefaultOnPaidPlans($key) && $this->plans->hasPaidPlan($organization)) { return true; } @@ -245,6 +400,7 @@ class SpecialtyModuleService */ public function memberAccessLevel(Organization $organization, ?Member $member, string $key): string { + $key = $this->normalizeKey($key); $cacheKey = $organization->id.'|'.($member?->id ?? 'guest').'|'.$key; if (isset($this->memberAccessLevelCache[$cacheKey])) { return $this->memberAccessLevelCache[$cacheKey]; @@ -294,6 +450,7 @@ class SpecialtyModuleService */ public function memberCanManage(Organization $organization, ?Member $member, string $key): bool { + $key = $this->normalizeKey($key); if (! $this->isEnabled($organization, $key) || ! $member) { return false; } @@ -358,6 +515,7 @@ class SpecialtyModuleService */ public function memberCanView(Organization $organization, ?Member $member, string $key): bool { + $key = $this->normalizeKey($key); if ($this->memberCanManage($organization, $member, $key) || $this->memberCanRefer($organization, $member, $key)) { return true; @@ -413,6 +571,7 @@ class SpecialtyModuleService */ public function memberCanRefer(Organization $organization, ?Member $member, string $key): bool { + $key = $this->normalizeKey($key); if ($this->memberCanManage($organization, $member, $key)) { return true; } diff --git a/app/Services/Care/SpecialtyShellService.php b/app/Services/Care/SpecialtyShellService.php index 422866e..dc6c948 100644 --- a/app/Services/Care/SpecialtyShellService.php +++ b/app/Services/Care/SpecialtyShellService.php @@ -13,7 +13,7 @@ use App\Services\Care\BloodBank\BloodBankWorkflowService; use App\Services\Care\Emergency\EmergencyWorkflowService; use App\Services\Care\Ophthalmology\OphthalmologyWorkflowService; use App\Services\Care\Physiotherapy\PhysiotherapyWorkflowService; -use App\Services\Care\Maternity\MaternityWorkflowService; +use App\Services\Care\WomensHealth\WomensHealthWorkflowService; use App\Services\Care\Radiology\RadiologyWorkflowService; use App\Services\Care\Cardiology\CardiologyWorkflowService; use App\Services\Care\Psychiatry\PsychiatryWorkflowService; @@ -48,6 +48,7 @@ class SpecialtyShellService */ public function definition(string $moduleKey): array { + $moduleKey = $this->modules->normalizeKey($moduleKey); $base = $this->modules->definition($moduleKey) ?? []; $shell = config("care_specialty_shell.modules.{$moduleKey}", []); $defaults = config('care_specialty_shell.defaults', []); @@ -76,7 +77,7 @@ class SpecialtyShellService 'blood_bank' => 'care.specialty.blood-bank.stage', 'ophthalmology' => 'care.specialty.ophthalmology.stage', 'physiotherapy' => 'care.specialty.physiotherapy.stage', - 'maternity' => 'care.specialty.maternity.stage', + 'womens_health' => 'care.specialty.womens_health.stage', 'radiology' => 'care.specialty.radiology.stage', 'cardiology' => 'care.specialty.cardiology.stage', 'psychiatry' => 'care.specialty.psychiatry.stage', @@ -110,7 +111,7 @@ class SpecialtyShellService 'blood_bank' => app(BloodBankWorkflowService::class)->stageFlow(), 'ophthalmology' => app(OphthalmologyWorkflowService::class)->stageFlow(), 'physiotherapy' => app(PhysiotherapyWorkflowService::class)->stageFlow(), - 'maternity' => app(MaternityWorkflowService::class)->stageFlow(), + 'womens_health' => app(WomensHealthWorkflowService::class)->stageFlow(), 'radiology' => app(RadiologyWorkflowService::class)->stageFlow(), 'cardiology' => app(CardiologyWorkflowService::class)->stageFlow(), 'psychiatry' => app(PsychiatryWorkflowService::class)->stageFlow(), @@ -179,33 +180,52 @@ class SpecialtyShellService /** * Catalog from config (authoritative template). * - * @return list + * @return list */ - public function catalogServices(string $moduleKey): array + public function catalogServices(string $moduleKey, ?Organization $organization = null): array { + $moduleKey = $this->modules->normalizeKey($moduleKey); $services = $this->definition($moduleKey)['services'] ?? []; if (! is_array($services)) { return []; } - return array_values(array_map(function ($row) { - return [ + $enabledLines = $organization + ? $this->modules->enabledServiceLines($organization, $moduleKey) + : []; + + $out = []; + foreach ($services as $row) { + if (! is_array($row)) { + continue; + } + $line = isset($row['service_line']) ? (string) $row['service_line'] : ''; + if ($organization && $enabledLines !== [] && $line !== '') { + if (! ($enabledLines[$line] ?? false)) { + continue; + } + } + $out[] = [ 'code' => (string) ($row['code'] ?? ''), 'label' => (string) ($row['label'] ?? ''), 'amount_minor' => (int) ($row['amount_minor'] ?? 0), 'type' => (string) ($row['type'] ?? 'misc'), + 'service_line' => $line, ]; - }, $services)); + } + + return array_values($out); } /** * Seeded services stored on the organization when the module was activated. * - * @return list + * @return list */ public function provisionedServices(Organization $organization, string $moduleKey): array { - $catalog = collect($this->catalogServices($moduleKey))->keyBy(fn ($s) => (string) ($s['code'] ?? '')); + $moduleKey = $this->modules->normalizeKey($moduleKey); + $catalog = collect($this->catalogServices($moduleKey, $organization))->keyBy(fn ($s) => (string) ($s['code'] ?? '')); $stored = data_get($organization->settings, "specialty_module_provisioning.{$moduleKey}.services"); if (is_array($stored)) { foreach ($stored as $service) { @@ -213,6 +233,11 @@ class SpecialtyShellService if ($code === '') { continue; } + // Skip stored services for disabled service lines. + $line = (string) ($service['service_line'] ?? $catalog->get($code)['service_line'] ?? ''); + if ($line !== '' && ! $this->modules->isServiceLineEnabled($organization, $moduleKey, $line)) { + continue; + } $prior = $catalog->get($code, []); $catalog->put($code, array_merge(is_array($prior) ? $prior : [], $service)); } @@ -427,7 +452,7 @@ class SpecialtyShellService ) { $code = (string) ($stage['code'] ?? ''); - if (in_array($moduleKey, ['dentistry', 'emergency', 'blood_bank', 'ophthalmology', 'physiotherapy', 'maternity', 'radiology', 'cardiology', 'psychiatry', 'pediatrics', 'orthopedics', 'ent', 'oncology', 'renal', 'surgery', 'vaccination', 'pathology', 'infusion', 'dermatology', 'podiatry', 'fertility', 'child_welfare', 'ambulance'], true) && $visitIds->isNotEmpty()) { + if (in_array($moduleKey, ['dentistry', 'emergency', 'blood_bank', 'ophthalmology', 'physiotherapy', 'womens_health', 'radiology', 'cardiology', 'psychiatry', 'pediatrics', 'orthopedics', 'ent', 'oncology', 'renal', 'surgery', 'vaccination', 'pathology', 'infusion', 'dermatology', 'podiatry', 'fertility', 'child_welfare', 'ambulance'], true) && $visitIds->isNotEmpty()) { $count = Visit::owned($ownerRef) ->where('organization_id', $organization->id) ->whereIn('id', $visitIds) @@ -459,7 +484,7 @@ class SpecialtyShellService 'blood_bank' => $clinical->countOpenByType($organization, 'blood_bank', 'blood_request', $branchScope), 'ophthalmology' => $clinical->countOpenByType($organization, 'ophthalmology', 'eye_exam', $branchScope), 'physiotherapy' => $clinical->countOpenByType($organization, 'physiotherapy', 'pt_assessment', $branchScope), - 'maternity' => $clinical->countOpenByType($organization, 'maternity', 'anc_history', $branchScope), + 'womens_health' => $clinical->countOpenByType($organization, 'womens_health', 'anc_history', $branchScope), 'radiology' => $clinical->countOpenByType($organization, 'radiology', 'imaging_request', $branchScope), 'cardiology' => $clinical->countOpenByType($organization, 'cardiology', 'cardiac_exam', $branchScope), 'psychiatry' => $clinical->countOpenByType($organization, 'psychiatry', 'mental_status', $branchScope), diff --git a/app/Services/Care/SpecialtyVisitStageService.php b/app/Services/Care/SpecialtyVisitStageService.php index 4fd1a09..cea2c52 100644 --- a/app/Services/Care/SpecialtyVisitStageService.php +++ b/app/Services/Care/SpecialtyVisitStageService.php @@ -112,11 +112,11 @@ class SpecialtyVisitStageService : ($stages[1] ?? ($stages[0] ?? null)); } - if ($moduleKey === 'maternity') { + if ($moduleKey === 'womens_health') { $stages = $this->allowedStages($moduleKey); - return in_array(\App\Services\Care\Maternity\MaternityWorkflowService::STAGE_HISTORY, $stages, true) - ? \App\Services\Care\Maternity\MaternityWorkflowService::STAGE_HISTORY + return in_array(\App\Services\Care\WomensHealth\WomensHealthWorkflowService::STAGE_HISTORY, $stages, true) + ? \App\Services\Care\WomensHealth\WomensHealthWorkflowService::STAGE_HISTORY : ($stages[1] ?? ($stages[0] ?? null)); } diff --git a/app/Services/Care/Maternity/MaternityAnalyticsService.php b/app/Services/Care/WomensHealth/WomensHealthAnalyticsService.php similarity index 95% rename from app/Services/Care/Maternity/MaternityAnalyticsService.php rename to app/Services/Care/WomensHealth/WomensHealthAnalyticsService.php index 712bfcc..879619b 100644 --- a/app/Services/Care/Maternity/MaternityAnalyticsService.php +++ b/app/Services/Care/WomensHealth/WomensHealthAnalyticsService.php @@ -1,6 +1,6 @@ startOfDay() : now()->startOfMonth(); $rangeTo = $to ? Carbon::parse($to)->endOfDay() : now()->endOfDay(); - $departmentIds = $this->shell->departmentIds($organization, 'maternity', $ownerRef, $branchId); + $departmentIds = $this->shell->departmentIds($organization, 'womens_health', $ownerRef, $branchId); $appointmentBase = Appointment::owned($ownerRef) ->where('organization_id', $organization->id) @@ -69,7 +69,7 @@ class MaternityAnalyticsService $highRiskOpen = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) - ->where('module_key', 'maternity') + ->where('module_key', 'womens_health') ->where('record_type', 'mat_plan') ->whereIn('visit_id', $openVisitIds->isEmpty() ? [0] : $openVisitIds) ->get() @@ -82,7 +82,7 @@ class MaternityAnalyticsService $planRecords = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) - ->where('module_key', 'maternity') + ->where('module_key', 'womens_health') ->where('record_type', 'mat_plan') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) @@ -100,7 +100,7 @@ class MaternityAnalyticsService $postnatalRecords = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) - ->where('module_key', 'maternity') + ->where('module_key', 'womens_health') ->where('record_type', 'postnatal_note') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) @@ -132,7 +132,7 @@ class MaternityAnalyticsService }), 1); } - $serviceLabels = collect($this->shell->provisionedServices($organization, 'maternity')) + $serviceLabels = collect($this->shell->provisionedServices($organization, 'womens_health')) ->pluck('label') ->filter() ->values() diff --git a/app/Services/Care/Maternity/MaternityWorkflowService.php b/app/Services/Care/WomensHealth/WomensHealthWorkflowService.php similarity index 95% rename from app/Services/Care/Maternity/MaternityWorkflowService.php rename to app/Services/Care/WomensHealth/WomensHealthWorkflowService.php index 16c65f5..071fd74 100644 --- a/app/Services/Care/Maternity/MaternityWorkflowService.php +++ b/app/Services/Care/WomensHealth/WomensHealthWorkflowService.php @@ -1,11 +1,11 @@ ['name' => 'Dr. Ama Dental', 'role' => 'dentist'], 'ophthalmology' => ['name' => 'Dr. Kofi Eye', 'role' => 'doctor'], 'physiotherapy' => ['name' => 'Dr. Efua Physio', 'role' => 'physiotherapist'], - 'maternity' => ['name' => 'Abena Midwife', 'role' => 'midwife'], + 'womens_health' => ['name' => 'Abena Midwife', 'role' => 'midwife'], 'radiology' => ['name' => 'Dr. Yaw Radiology', 'role' => 'radiologist'], 'cardiology' => ['name' => 'Dr. Kwesi Cardiology', 'role' => 'doctor'], 'psychiatry' => ['name' => 'Dr. Adwoa Psychiatry', 'role' => 'psychiatrist'], diff --git a/config/care.php b/config/care.php index a50adfd..f9d61b9 100644 --- a/config/care.php +++ b/config/care.php @@ -24,6 +24,10 @@ return [ 'psychiatrist' => 'Psychiatrist', 'physiotherapist' => 'Physiotherapist', 'midwife' => 'Midwife', + 'obstetrician' => 'Obstetrician', + 'gynecologist' => 'Gynecologist', + 'obgyn' => 'OB/GYN Consultant', + 'labour_ward_nurse' => 'Labour Ward Nurse', 'pediatrician' => 'Pediatrician', 'oncologist' => 'Oncologist', 'cardiologist' => 'Cardiologist', @@ -33,6 +37,8 @@ return [ 'orthopedist' => 'Orthopedist', 'podiatrist' => 'Podiatrist', 'fertility_specialist' => 'Fertility Specialist', + 'embryologist' => 'Embryologist', + 'fertility_nurse' => 'Fertility Nurse', 'dialysis_nurse' => 'Dialysis Nurse', 'ambulance_staff' => 'Ambulance Staff', 'nurse' => 'Nurse (legacy)', @@ -49,7 +55,8 @@ return [ 'laboratory' => 'Laboratory', 'radiology' => 'Radiology', 'pharmacy' => 'Pharmacy', - 'maternity' => 'Maternity', + 'womens_health' => "Women's Health (OB/GYN)", + 'maternity' => 'Maternity (legacy)', 'dental' => 'Dental', 'ophthalmology' => 'Ophthalmology / Eye care', 'physiotherapy' => 'Physiotherapy', @@ -67,7 +74,7 @@ return [ 'infusion' => 'Infusion Centre', 'dermatology' => 'Dermatology', 'podiatry' => 'Podiatry', - 'fertility' => 'Fertility', + 'fertility' => 'Fertility & Reproductive Medicine', 'child_welfare' => 'Child Welfare Clinic', 'ambulance' => 'Ambulance', ], @@ -85,7 +92,8 @@ return [ 'Laboratory', 'Radiology', 'Pharmacy', - 'Maternity', + "Women's Health (OB/GYN)", + 'Maternity / Labour & Delivery', 'Dentistry', 'Ophthalmology / Eye care', 'Physiotherapy', @@ -313,7 +321,8 @@ return [ 'specialty_dentistry' => 'Dental document', 'specialty_ophthalmology' => 'Eye care document', 'specialty_physiotherapy' => 'Physiotherapy document', - 'specialty_maternity' => 'Maternity document', + 'specialty_maternity' => "Women's Health document", + 'specialty_womens_health' => "Women's Health document", 'other' => 'Other', ], diff --git a/config/care_specialty_clinical.php b/config/care_specialty_clinical.php index d5e0d34..79730e5 100644 --- a/config/care_specialty_clinical.php +++ b/config/care_specialty_clinical.php @@ -38,7 +38,7 @@ return [ 'session' => 'pt_session', 'exercises' => 'pt_home_program', ], - 'maternity' => [ + 'womens_health' => [ 'history' => 'anc_history', 'exam' => 'obstetric_exam', 'fetal' => 'fetal_notes', @@ -341,7 +341,7 @@ return [ ['name' => 'next_review', 'label' => 'Next review', 'type' => 'text'], ], ], - 'maternity' => [ + 'womens_health' => [ 'anc_history' => [ ['name' => 'gravida', 'label' => 'Gravida', 'type' => 'number', 'required' => true], ['name' => 'para', 'label' => 'Para', 'type' => 'number'], diff --git a/config/care_specialty_modules.php b/config/care_specialty_modules.php index e98633b..eef5867 100644 --- a/config/care_specialty_modules.php +++ b/config/care_specialty_modules.php @@ -113,25 +113,44 @@ return [ 'refer_roles' => ['general_physician', 'doctor', 'receptionist'], 'specialist_keywords' => ['physio', 'rehab', 'therapy'], ], - 'maternity' => [ - 'label' => 'Maternity / Antenatal', - 'description' => 'Antenatal clinics, maternity wards, and related queues.', - 'department_type' => 'maternity', - 'department_name' => 'Maternity', - 'queue_name' => 'Maternity', - 'queue_prefix' => 'MAT', - 'nav_label' => 'Maternity', + 'womens_health' => [ + 'label' => "Women's Health (OB/GYN)", + 'description' => 'Gynecology, obstetrics, antenatal, labour & delivery, postnatal, and related women\'s health clinics.', + 'department_type' => 'womens_health', + 'department_name' => "Women's Health", + 'queue_name' => "Women's Health", + 'queue_prefix' => 'WH', + 'nav_label' => "Women's Health", 'icon' => 'home', - 'queue_keywords' => ['matern', 'antenatal', 'obstetric'], + 'queue_keywords' => ['women', 'obgyn', 'ob/gyn', 'gynae', 'gynec', 'matern', 'antenatal', 'obstetric', 'labour', 'labor'], 'access' => 'limited', - 'roles' => ['midwife', 'nurse'], + 'roles' => [ + 'obstetrician', 'gynecologist', 'obgyn', 'midwife', 'labour_ward_nurse', 'nurse', + ], 'view_roles' => [ - 'midwife', 'nurse', 'general_physician', 'doctor', 'pharmacist', 'receptionist', + 'obstetrician', 'gynecologist', 'obgyn', 'midwife', 'labour_ward_nurse', 'nurse', + 'general_physician', 'doctor', 'pharmacist', 'receptionist', ], 'refer_roles' => [ 'general_physician', 'doctor', 'pharmacist', 'receptionist', ], - 'specialist_keywords' => ['matern', 'antenatal', 'obstetric', 'midwif'], + 'specialist_keywords' => [ + 'women', 'obgyn', 'ob/gyn', 'gynae', 'gynecolog', 'obstetric', 'matern', 'antenatal', 'midwif', + ], + // Configurable service lines (Settings → Modules → Women's Health). + // All default on so migrated maternity hospitals keep full surface. + 'service_lines' => [ + 'gynecology' => ['label' => 'Gynecology', 'default_on' => true], + 'obstetrics' => ['label' => 'Obstetrics', 'default_on' => true], + 'antenatal' => ['label' => 'Antenatal Clinic', 'default_on' => true], + 'labour_delivery' => ['label' => 'Labour & Delivery (Maternity)', 'default_on' => true], + 'postnatal' => ['label' => 'Postnatal Care', 'default_on' => true], + 'family_planning' => ['label' => 'Family Planning', 'default_on' => true], + 'cervical_screening' => ['label' => 'Cervical Screening', 'default_on' => true], + 'menopause' => ['label' => 'Menopause Clinic', 'default_on' => true], + 'high_risk_pregnancy' => ['label' => 'High-Risk Pregnancy', 'default_on' => true], + 'early_pregnancy' => ['label' => 'Early Pregnancy Assessment', 'default_on' => true], + ], ], 'radiology' => [ 'label' => 'Radiology', @@ -415,23 +434,26 @@ return [ 'specialist_keywords' => ['podiat', 'foot'], ], 'fertility' => [ - 'label' => 'Fertility', - 'description' => 'Fertility / IVF clinics and reproductive health queues.', + 'label' => 'Fertility & Reproductive Medicine', + 'description' => 'IVF / fertility clinics, embryology, and reproductive medicine queues. Integrates with Women\'s Health via referrals.', 'department_type' => 'fertility', 'department_name' => 'Fertility', 'queue_name' => 'Fertility', 'queue_prefix' => 'FER', 'nav_label' => 'Fertility', 'icon' => 'sparkles', - 'queue_keywords' => ['fertil', 'ivf', 'reproduct'], + 'queue_keywords' => ['fertil', 'ivf', 'reproduct', 'embryol'], 'access' => 'restricted', - 'roles' => ['fertility_specialist'], + 'roles' => ['fertility_specialist', 'embryologist', 'fertility_nurse'], 'support_roles' => [], 'view_roles' => [ - 'fertility_specialist', 'general_physician', 'doctor', + 'fertility_specialist', 'embryologist', 'fertility_nurse', + 'general_physician', 'doctor', 'obstetrician', 'gynecologist', 'obgyn', ], - 'refer_roles' => ['general_physician', 'doctor'], - 'specialist_keywords' => ['fertil', 'ivf', 'reproduct'], + 'refer_roles' => [ + 'general_physician', 'doctor', 'obstetrician', 'gynecologist', 'obgyn', 'midwife', + ], + 'specialist_keywords' => ['fertil', 'ivf', 'reproduct', 'embryol'], ], 'child_welfare' => [ 'label' => 'Child Welfare Clinic', diff --git a/config/care_specialty_shell.php b/config/care_specialty_shell.php index 558a587..41c76dd 100644 --- a/config/care_specialty_shell.php +++ b/config/care_specialty_shell.php @@ -250,7 +250,7 @@ return [ 'completed' => 'overview', ], ], - 'maternity' => [ + 'womens_health' => [ 'stages' => [ ['code' => 'check_in', 'label' => 'Check-in', 'queue_point' => 'waiting'], ['code' => 'history', 'label' => 'ANC history', 'queue_point' => 'waiting'], @@ -261,12 +261,19 @@ return [ ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], ], 'services' => [ - ['code' => 'mat.anc', 'label' => 'Antenatal visit', 'amount_minor' => 5000, 'type' => 'consultation'], - ['code' => 'mat.history', 'label' => 'Obstetric history intake', 'amount_minor' => 3500, 'type' => 'consultation'], - ['code' => 'mat.exam', 'label' => 'Obstetric examination', 'amount_minor' => 4500, 'type' => 'procedure'], - ['code' => 'mat.ultrasound', 'label' => 'Obstetric ultrasound', 'amount_minor' => 12000, 'type' => 'imaging'], - ['code' => 'mat.labs', 'label' => 'ANC investigations panel', 'amount_minor' => 8000, 'type' => 'procedure'], - ['code' => 'mat.postnatal', 'label' => 'Postnatal review', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'mat.anc', 'service_line' => 'antenatal', 'label' => 'Antenatal visit', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'mat.history', 'service_line' => 'antenatal', 'label' => 'Obstetric history intake', 'amount_minor' => 3500, 'type' => 'consultation'], + ['code' => 'mat.exam', 'service_line' => 'obstetrics', 'label' => 'Obstetric examination', 'amount_minor' => 4500, 'type' => 'procedure'], + ['code' => 'mat.ultrasound', 'service_line' => 'obstetrics', 'label' => 'Obstetric ultrasound', 'amount_minor' => 12000, 'type' => 'imaging'], + ['code' => 'mat.labs', 'service_line' => 'antenatal', 'label' => 'ANC investigations panel', 'amount_minor' => 8000, 'type' => 'procedure'], + ['code' => 'mat.postnatal', 'service_line' => 'postnatal', 'label' => 'Postnatal review', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'wh.gyn.consult', 'service_line' => 'gynecology', 'label' => 'Gynecology consultation', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'wh.fp.consult', 'service_line' => 'family_planning', 'label' => 'Family planning visit', 'amount_minor' => 3500, 'type' => 'consultation'], + ['code' => 'wh.cervical', 'service_line' => 'cervical_screening', 'label' => 'Cervical screening', 'amount_minor' => 4500, 'type' => 'procedure'], + ['code' => 'wh.menopause', 'service_line' => 'menopause', 'label' => 'Menopause clinic visit', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'wh.high_risk', 'service_line' => 'high_risk_pregnancy', 'label' => 'High-risk pregnancy review', 'amount_minor' => 6000, 'type' => 'consultation'], + ['code' => 'wh.epa', 'service_line' => 'early_pregnancy', 'label' => 'Early pregnancy assessment', 'amount_minor' => 5500, 'type' => 'consultation'], + ['code' => 'wh.labour', 'service_line' => 'labour_delivery', 'label' => 'Labour & delivery attendance', 'amount_minor' => 15000, 'type' => 'procedure'], ], 'workspace_tabs' => [ 'overview' => 'Overview', diff --git a/config/care_workflows.php b/config/care_workflows.php index 2c75567..97e56d3 100644 --- a/config/care_workflows.php +++ b/config/care_workflows.php @@ -77,8 +77,8 @@ return [ 'modules' => ['pharmacy', 'billing'], 'default_template' => 'cashless_clinic', ], - 'maternity' => [ - 'label' => 'Maternity home', + 'womens_health' => [ + 'label' => "Women's Health home", 'modules' => ['lab', 'pharmacy', 'billing'], 'default_template' => 'private_hospital', ], diff --git a/resources/views/care/settings/edit.blade.php b/resources/views/care/settings/edit.blade.php index 5e113cc..0a035e3 100644 --- a/resources/views/care/settings/edit.blade.php +++ b/resources/views/care/settings/edit.blade.php @@ -156,7 +156,7 @@ @if (empty($canUseSpecialtyModules)) Pro @endif - Dentistry, eye care, physiotherapy, maternity, radiology — activate & set prices + Dentistry, eye care, physiotherapy, women's health, radiology — activate & set prices diff --git a/resources/views/care/settings/module-show.blade.php b/resources/views/care/settings/module-show.blade.php index 74d07a2..e34f265 100644 --- a/resources/views/care/settings/module-show.blade.php +++ b/resources/views/care/settings/module-show.blade.php @@ -28,6 +28,37 @@ + @if (! empty($serviceLines) && $canUseSpecialtyModules) + +
+ @csrf + @method('PUT') +
+ @foreach ($serviceLines as $lineKey => $line) + + @endforeach +
+ @if ($canManage) +
+ +
+ @endif +
+
+ @endif + @if (! $canUseSpecialtyModules) View plans diff --git a/resources/views/care/specialty/partials/actions-menu.blade.php b/resources/views/care/specialty/partials/actions-menu.blade.php index cc78de5..fd69767 100644 --- a/resources/views/care/specialty/partials/actions-menu.blade.php +++ b/resources/views/care/specialty/partials/actions-menu.blade.php @@ -46,7 +46,7 @@ 'dentistry' => 'chair', 'ophthalmology' => 'check_in', 'physiotherapy' => 'check_in', - 'maternity' => 'check_in', + 'womens_health' => 'check_in', 'radiology' => 'check_in', 'cardiology' => 'check_in', 'psychiatry' => 'check_in', @@ -66,7 +66,7 @@ 'dentistry' => 'Seat at chair', 'ophthalmology' => 'Start check-in', 'physiotherapy' => 'Start check-in', - 'maternity' => 'Start check-in', + 'womens_health' => 'Start check-in', 'radiology' => 'Start check-in', 'cardiology' => 'Start check-in', 'psychiatry' => 'Start check-in', diff --git a/resources/views/care/specialty/sections/workspace.blade.php b/resources/views/care/specialty/sections/workspace.blade.php index 4fcf6b9..82cefe8 100644 --- a/resources/views/care/specialty/sections/workspace.blade.php +++ b/resources/views/care/specialty/sections/workspace.blade.php @@ -65,8 +65,8 @@ @include('care.specialty.ophthalmology.workspace-'.$activeTab) @elseif ($moduleKey === 'physiotherapy' && in_array($activeTab, ['overview', 'session'], true)) @include('care.specialty.physiotherapy.workspace-'.$activeTab) - @elseif ($moduleKey === 'maternity' && in_array($activeTab, ['overview', 'postnatal'], true)) - @include('care.specialty.maternity.workspace-'.$activeTab) + @elseif ($moduleKey === 'womens_health' && in_array($activeTab, ['overview', 'postnatal'], true)) + @include('care.specialty.womens_health.workspace-'.$activeTab) @elseif ($moduleKey === 'radiology' && in_array($activeTab, ['overview', 'verification'], true)) @include('care.specialty.radiology.workspace-'.$activeTab) @elseif ($moduleKey === 'cardiology' && in_array($activeTab, ['overview', 'procedures'], true)) diff --git a/resources/views/care/specialty/shell.blade.php b/resources/views/care/specialty/shell.blade.php index 0de2ae0..3275818 100644 --- a/resources/views/care/specialty/shell.blade.php +++ b/resources/views/care/specialty/shell.blade.php @@ -40,8 +40,8 @@ @if ($moduleKey === 'physiotherapy') Reports @endif - @if ($moduleKey === 'maternity') - Reports + @if ($moduleKey === 'womens_health') + Reports @endif @if ($moduleKey === 'radiology') Reports diff --git a/resources/views/care/specialty/maternity/print.blade.php b/resources/views/care/specialty/womens_health/print.blade.php similarity index 96% rename from resources/views/care/specialty/maternity/print.blade.php rename to resources/views/care/specialty/womens_health/print.blade.php index 4570858..6b896d8 100644 --- a/resources/views/care/specialty/maternity/print.blade.php +++ b/resources/views/care/specialty/womens_health/print.blade.php @@ -2,7 +2,7 @@ - Maternity summary · {{ $patient->fullName() }} + Women's Health summary · {{ $patient->fullName() }} -

Back

+

Back

{{ $patient->fullName() }}

diff --git a/resources/views/care/specialty/maternity/reports.blade.php b/resources/views/care/specialty/womens_health/reports.blade.php similarity index 91% rename from resources/views/care/specialty/maternity/reports.blade.php rename to resources/views/care/specialty/womens_health/reports.blade.php index 0ec7204..65f7649 100644 --- a/resources/views/care/specialty/maternity/reports.blade.php +++ b/resources/views/care/specialty/womens_health/reports.blade.php @@ -1,11 +1,11 @@ - +

-

Maternity reports

-

Arrivals, high-risk ANC, risk categories, postnatal outcomes, length of stay, and maternity service revenue.

+

Women's Health reports

+

Arrivals, high-risk ANC, risk categories, postnatal outcomes, length of stay, and Women's Health service revenue.

- ← Specialty home + ← Specialty home
@@ -72,7 +72,7 @@
-

Maternity service revenue

+

Women's Health service revenue

    @forelse ($report['revenue_by_service'] as $row)
  • @@ -80,7 +80,7 @@ {{ $currency }} {{ number_format($row->amount_minor / 100, 2) }}
  • @empty -
  • No billed maternity services in range.
  • +
  • No billed Women's Health services in range.
  • @endforelse
diff --git a/resources/views/care/specialty/maternity/workspace-overview.blade.php b/resources/views/care/specialty/womens_health/workspace-overview.blade.php similarity index 90% rename from resources/views/care/specialty/maternity/workspace-overview.blade.php rename to resources/views/care/specialty/womens_health/workspace-overview.blade.php index ad79c08..fd03e6e 100644 --- a/resources/views/care/specialty/maternity/workspace-overview.blade.php +++ b/resources/views/care/specialty/womens_health/workspace-overview.blade.php @@ -8,12 +8,12 @@
-

Maternity overview

+

Women's Health overview

ANC history, exam, fetal notes, and care plan for this episode.

diff --git a/resources/views/care/specialty/maternity/workspace-postnatal.blade.php b/resources/views/care/specialty/womens_health/workspace-postnatal.blade.php similarity index 92% rename from resources/views/care/specialty/maternity/workspace-postnatal.blade.php rename to resources/views/care/specialty/womens_health/workspace-postnatal.blade.php index ed67e25..c96cb13 100644 --- a/resources/views/care/specialty/maternity/workspace-postnatal.blade.php +++ b/resources/views/care/specialty/womens_health/workspace-postnatal.blade.php @@ -10,11 +10,11 @@

Delivery / postnatal

Clinic delivery notes and postnatal review. Discharge outcomes can close the visit.

- Print summary + Print summary @if ($canManage) - + @csrf
diff --git a/routes/web.php b/routes/web.php index 2d32fc8..4b52d58 100644 --- a/routes/web.php +++ b/routes/web.php @@ -43,7 +43,7 @@ use App\Http\Controllers\Care\LabAdminController; use App\Http\Controllers\Care\EmergencyWorkspaceController; use App\Http\Controllers\Care\OphthalmologyWorkspaceController; use App\Http\Controllers\Care\PhysiotherapyWorkspaceController; -use App\Http\Controllers\Care\MaternityWorkspaceController; +use App\Http\Controllers\Care\WomensHealthWorkspaceController; use App\Http\Controllers\Care\RadiologyWorkspaceController; use App\Http\Controllers\Care\CardiologyWorkspaceController; use App\Http\Controllers\Care\PsychiatryWorkspaceController; @@ -145,7 +145,8 @@ Route::middleware(['auth', 'platform.session'])->group(function () { Route::get('/specialty/blood-bank/reports', [BloodBankWorkspaceController::class, 'reports'])->name('care.specialty.blood-bank.reports'); Route::get('/specialty/ophthalmology/reports', [OphthalmologyWorkspaceController::class, 'reports'])->name('care.specialty.ophthalmology.reports'); Route::get('/specialty/physiotherapy/reports', [PhysiotherapyWorkspaceController::class, 'reports'])->name('care.specialty.physiotherapy.reports'); - Route::get('/specialty/maternity/reports', [MaternityWorkspaceController::class, 'reports'])->name('care.specialty.maternity.reports'); + Route::get('/specialty/womens-health/reports', [WomensHealthWorkspaceController::class, 'reports'])->name('care.specialty.womens_health.reports'); + Route::get('/specialty/maternity/reports', fn () => redirect()->route('care.specialty.womens_health.reports')); Route::get('/specialty/radiology/reports', [RadiologyWorkspaceController::class, 'reports'])->name('care.specialty.radiology.reports'); Route::get('/specialty/cardiology/reports', [CardiologyWorkspaceController::class, 'reports'])->name('care.specialty.cardiology.reports'); Route::get('/specialty/psychiatry/reports', [PsychiatryWorkspaceController::class, 'reports'])->name('care.specialty.psychiatry.reports'); @@ -202,9 +203,9 @@ Route::middleware(['auth', 'platform.session'])->group(function () { Route::post('/specialty/physiotherapy/workspace/{visit}/stage', [PhysiotherapyWorkspaceController::class, 'setStage'])->name('care.specialty.physiotherapy.stage'); Route::post('/specialty/physiotherapy/workspace/{visit}/session', [PhysiotherapyWorkspaceController::class, 'saveSession'])->name('care.specialty.physiotherapy.session'); Route::get('/specialty/physiotherapy/workspace/{visit}/print', [PhysiotherapyWorkspaceController::class, 'printSummary'])->name('care.specialty.physiotherapy.print'); - Route::post('/specialty/maternity/workspace/{visit}/stage', [MaternityWorkspaceController::class, 'setStage'])->name('care.specialty.maternity.stage'); - Route::post('/specialty/maternity/workspace/{visit}/postnatal', [MaternityWorkspaceController::class, 'savePostnatal'])->name('care.specialty.maternity.postnatal'); - Route::get('/specialty/maternity/workspace/{visit}/print', [MaternityWorkspaceController::class, 'printSummary'])->name('care.specialty.maternity.print'); + Route::post('/specialty/womens-health/workspace/{visit}/stage', [WomensHealthWorkspaceController::class, 'setStage'])->name('care.specialty.womens_health.stage'); + Route::post('/specialty/womens-health/workspace/{visit}/postnatal', [WomensHealthWorkspaceController::class, 'savePostnatal'])->name('care.specialty.womens_health.postnatal'); + Route::get('/specialty/womens-health/workspace/{visit}/print', [WomensHealthWorkspaceController::class, 'printSummary'])->name('care.specialty.womens_health.print'); Route::post('/specialty/radiology/workspace/{visit}/stage', [RadiologyWorkspaceController::class, 'setStage'])->name('care.specialty.radiology.stage'); Route::post('/specialty/radiology/workspace/{visit}/verification', [RadiologyWorkspaceController::class, 'saveVerification'])->name('care.specialty.radiology.verification'); Route::get('/specialty/radiology/workspace/{visit}/print', [RadiologyWorkspaceController::class, 'printSummary'])->name('care.specialty.radiology.print'); @@ -360,6 +361,7 @@ Route::middleware(['auth', 'platform.session'])->group(function () { Route::put('/settings/modules', [SettingsModulesController::class, 'update'])->name('care.settings.modules.update'); Route::get('/settings/modules/{module}', [SettingsModulesController::class, 'show'])->name('care.settings.modules.show'); Route::put('/settings/modules/{module}/services', [SettingsModulesController::class, 'updateServices'])->name('care.settings.modules.services.update'); + Route::put('/settings/modules/{module}/service-lines', [SettingsModulesController::class, 'updateServiceLines'])->name('care.settings.modules.service-lines.update'); Route::get('/integrations', [IntegrationsController::class, 'edit'])->name('care.integrations'); Route::post('/integrations/sms', [IntegrationsController::class, 'saveSms'])->name('care.integrations.sms.save'); diff --git a/tests/Feature/CareSpecialtyAccessTest.php b/tests/Feature/CareSpecialtyAccessTest.php index cc6e23a..b0a6448 100644 --- a/tests/Feature/CareSpecialtyAccessTest.php +++ b/tests/Feature/CareSpecialtyAccessTest.php @@ -95,7 +95,7 @@ class CareSpecialtyAccessTest extends TestCase public function test_nurse_and_pharmacist_manage_general_modules(): void { - foreach (['vaccination', 'child_welfare', 'maternity'] as $key) { + foreach (['vaccination', 'child_welfare', 'womens_health'] as $key) { $this->modules->activate($this->organization->fresh(), $this->owner->public_id, $key); } $this->organization->refresh(); @@ -106,12 +106,12 @@ class CareSpecialtyAccessTest extends TestCase [, $lab] = $this->makeStaff('lab_technician', 'lab1'); [, $receptionist] = $this->makeStaff('receptionist', 'rec1'); - // Legacy floor nurse: vaccination / CWC / infusion / maternity manage; emergency refer only. + // Legacy floor nurse: vaccination / CWC / infusion / Women's Health manage; emergency refer only. $this->assertFalse($this->modules->memberCanManage($this->organization, $nurse, 'emergency')); $this->assertTrue($this->modules->memberCanRefer($this->organization, $nurse, 'emergency')); $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'vaccination')); $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'infusion')); - $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'maternity')); + $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'womens_health')); // 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')); diff --git a/tests/Feature/CareSpecialtyModulesTest.php b/tests/Feature/CareSpecialtyModulesTest.php index 6606ee9..d235bf5 100644 --- a/tests/Feature/CareSpecialtyModulesTest.php +++ b/tests/Feature/CareSpecialtyModulesTest.php @@ -207,7 +207,7 @@ class CareSpecialtyModulesTest extends TestCase ->assertSee('Dentistry') ->assertSee('Eye care') ->assertSee('Physiotherapy') - ->assertSee('Maternity') + ->assertSee("Women's Health") ->assertSee('Radiology'); } diff --git a/tests/Feature/CareSpecialtyShellTest.php b/tests/Feature/CareSpecialtyShellTest.php index ccb2130..a6b2453 100644 --- a/tests/Feature/CareSpecialtyShellTest.php +++ b/tests/Feature/CareSpecialtyShellTest.php @@ -91,8 +91,8 @@ class CareSpecialtyShellTest extends TestCase $this->assertSame('assessment', $shell->workspaceTabForStage('physiotherapy', 'assessment')); $this->assertSame('session', $shell->workspaceTabForStage('physiotherapy', 'session')); $this->assertSame('exercises', $shell->workspaceTabForStage('physiotherapy', 'progress')); - $this->assertSame('history', $shell->workspaceTabForStage('maternity', 'history')); - $this->assertSame('postnatal', $shell->workspaceTabForStage('maternity', 'postnatal')); + $this->assertSame('history', $shell->workspaceTabForStage('womens_health', 'history')); + $this->assertSame('postnatal', $shell->workspaceTabForStage('womens_health', 'postnatal')); $this->assertSame('odontogram', $shell->workspaceTabForStage('dentistry', 'chair')); $this->assertSame('issue', $shell->workspaceTabForStage('blood_bank', 'issue')); } diff --git a/tests/Feature/CareWomensHealthAccessTest.php b/tests/Feature/CareWomensHealthAccessTest.php new file mode 100644 index 0000000..c6bf11f --- /dev/null +++ b/tests/Feature/CareWomensHealthAccessTest.php @@ -0,0 +1,157 @@ +withoutMiddleware(EnsurePlatformSession::class); + + $this->owner = User::create([ + 'public_id' => 'wh-access-owner', + 'name' => 'Owner', + 'email' => 'wh-access-owner@example.com', + ]); + + $this->organization = Organization::create([ + 'owner_ref' => $this->owner->public_id, + 'name' => 'WH Clinic', + 'slug' => 'wh-access-clinic', + 'settings' => [ + 'onboarded' => true, + 'plan' => 'pro', + 'plan_expires_at' => now()->addMonth()->toIso8601String(), + ], + ]); + + Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $this->owner->public_id, + 'role' => 'hospital_admin', + ]); + + 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->activate($this->organization, $this->owner->public_id, 'womens_health'); + $this->modules->activate($this->organization->fresh(), $this->owner->public_id, 'fertility'); + $this->organization->refresh(); + } + + protected function makeStaff(string $role, string $suffix): Member + { + $user = User::create([ + 'public_id' => 'wh-'.$suffix, + 'name' => $role, + 'email' => $suffix.'@example.com', + ]); + + return Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $user->public_id, + 'role' => $role, + ]); + } + + public function test_obgyn_roles_manage_womens_health(): void + { + foreach (['obstetrician', 'gynecologist', 'obgyn', 'midwife', 'labour_ward_nurse'] as $role) { + $member = $this->makeStaff($role, $role.'1'); + $this->assertTrue( + $this->modules->memberCanManage($this->organization, $member, 'womens_health'), + "{$role} should manage womens_health", + ); + $this->assertFalse( + $this->modules->memberCanManage($this->organization, $member, 'fertility'), + "{$role} should not manage fertility", + ); + } + } + + public function test_fertility_roles_manage_fertility_only(): void + { + foreach (['fertility_specialist', 'embryologist', 'fertility_nurse'] as $role) { + $member = $this->makeStaff($role, $role.'1'); + $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'fertility')); + $this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'womens_health')); + } + } + + public function test_maternity_settings_migrate_to_womens_health(): void + { + $org = Organization::create([ + 'owner_ref' => $this->owner->public_id, + 'name' => 'Legacy Mat', + 'slug' => 'legacy-mat', + 'settings' => [ + 'onboarded' => true, + 'plan' => 'pro', + 'plan_expires_at' => now()->addMonth()->toIso8601String(), + 'specialty_modules' => ['maternity' => true], + 'specialty_module_provisioning' => [ + 'maternity' => ['active' => true, 'services' => [['code' => 'mat.anc', 'label' => 'ANC', 'amount_minor' => 100, 'type' => 'consultation']]], + ], + ], + ]); + + $this->assertTrue($this->modules->isEnabled($org, 'womens_health')); + $org->refresh(); + $this->assertTrue((bool) data_get($org->settings, 'specialty_modules.womens_health')); + $this->assertFalse((bool) data_get($org->settings, 'specialty_modules.maternity')); + $this->assertNotNull(data_get($org->settings, 'specialty_module_provisioning.womens_health')); + $this->assertNull(data_get($org->settings, 'specialty_module_provisioning.maternity')); + $this->assertSame('womens_health', $this->modules->normalizeKey('maternity')); + } + + public function test_service_line_toggle_filters_catalog_services(): void + { + $shell = app(SpecialtyShellService::class); + $before = collect($shell->catalogServices('womens_health', $this->organization))->pluck('code')->all(); + $this->assertContains('wh.gyn.consult', $before); + + $this->modules->syncServiceLines($this->organization, 'womens_health', [ + 'gynecology' => false, + 'obstetrics' => true, + 'antenatal' => true, + 'labour_delivery' => true, + 'postnatal' => true, + 'family_planning' => false, + 'cervical_screening' => false, + 'menopause' => false, + 'high_risk_pregnancy' => false, + 'early_pregnancy' => false, + ]); + $this->organization->refresh(); + + $after = collect($shell->catalogServices('womens_health', $this->organization->fresh()))->pluck('code')->all(); + $this->assertNotContains('wh.gyn.consult', $after); + $this->assertContains('mat.anc', $after); + } +} diff --git a/tests/Feature/CareMaternitySuiteTest.php b/tests/Feature/CareWomensHealthSuiteTest.php similarity index 86% rename from tests/Feature/CareMaternitySuiteTest.php rename to tests/Feature/CareWomensHealthSuiteTest.php index 1bb8223..2b6e2ff 100644 --- a/tests/Feature/CareMaternitySuiteTest.php +++ b/tests/Feature/CareWomensHealthSuiteTest.php @@ -16,7 +16,7 @@ use App\Services\Care\SpecialtyModuleService; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; -class CareMaternitySuiteTest extends TestCase +class CareWomensHealthSuiteTest extends TestCase { use RefreshDatabase; @@ -36,15 +36,15 @@ class CareMaternitySuiteTest extends TestCase $this->withoutMiddleware(EnsurePlatformSession::class); $this->owner = User::create([ - 'public_id' => 'mat-owner', + 'public_id' => 'wh-owner', 'name' => 'Owner', - 'email' => 'mat-owner@example.com', + 'email' => 'wh-owner@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->owner->public_id, - 'name' => 'Maternity Clinic', - 'slug' => 'maternity-clinic', + 'name' => "Women's Health Clinic", + 'slug' => 'womens-health-clinic', 'settings' => [ 'onboarded' => true, 'facility_type' => 'clinic', @@ -72,12 +72,12 @@ class CareMaternitySuiteTest extends TestCase app(SpecialtyModuleService::class)->activate( $this->organization, $this->owner->public_id, - 'maternity', + 'womens_health', ); $department = Department::query() ->where('branch_id', $this->branch->id) - ->where('type', 'maternity') + ->where('type', 'womens_health') ->firstOrFail(); $this->patient = Patient::create([ @@ -121,21 +121,21 @@ class CareMaternitySuiteTest extends TestCase { $this->actingAs($this->owner) ->get(route('care.specialty.workspace', [ - 'module' => 'maternity', + 'module' => 'womens_health', 'visit' => $this->visit, 'tab' => 'overview', ])) ->assertOk() - ->assertSee('Maternity overview') + ->assertSee('Health overview') ->assertSee('data-care-stage-bar', false) ->assertSee('Check-in') ->assertSee('ANC history') ->assertSee('Start ANC history') - ->assertSee('Maternity reports'); + ->assertSee('Health reports'); $this->actingAs($this->owner) ->get(route('care.specialty.workspace', [ - 'module' => 'maternity', + 'module' => 'womens_health', 'visit' => $this->visit, 'tab' => 'history', ])) @@ -148,7 +148,7 @@ class CareMaternitySuiteTest extends TestCase { $this->actingAs($this->owner) ->post(route('care.specialty.clinical.save', [ - 'module' => 'maternity', + 'module' => 'womens_health', 'visit' => $this->visit, ]), [ 'tab' => 'exam', @@ -177,11 +177,11 @@ class CareMaternitySuiteTest extends TestCase public function test_stage_advance_and_postnatal_completes_visit(): void { $this->actingAs($this->owner) - ->post(route('care.specialty.maternity.stage', $this->visit), [ + ->post(route('care.specialty.womens_health.stage', $this->visit), [ 'stage' => 'postnatal', ]) ->assertRedirect(route('care.specialty.workspace', [ - 'module' => 'maternity', + 'module' => 'womens_health', 'visit' => $this->visit, 'tab' => 'postnatal', ])); @@ -189,7 +189,7 @@ class CareMaternitySuiteTest extends TestCase $this->assertSame('postnatal', $this->visit->fresh()->specialty_stage); $this->actingAs($this->owner) - ->post(route('care.specialty.maternity.postnatal', $this->visit), [ + ->post(route('care.specialty.womens_health.postnatal', $this->visit), [ 'tab' => 'postnatal', 'payload' => [ 'episode_type' => 'Postnatal review', @@ -201,7 +201,7 @@ class CareMaternitySuiteTest extends TestCase ], ]) ->assertRedirect(route('care.specialty.workspace', [ - 'module' => 'maternity', + 'module' => 'womens_health', 'visit' => $this->visit, 'tab' => 'postnatal', ])); @@ -218,23 +218,23 @@ class CareMaternitySuiteTest extends TestCase public function test_reports_and_print(): void { $this->actingAs($this->owner) - ->get(route('care.specialty.maternity.reports')) + ->get(route('care.specialty.womens_health.reports')) ->assertOk() - ->assertSee('Maternity reports') + ->assertSee('Health reports') ->assertSee('Arrivals today'); $this->actingAs($this->owner) - ->get(route('care.specialty.maternity.print', $this->visit)) + ->get(route('care.specialty.womens_health.print', $this->visit)) ->assertOk() - ->assertSee('Maternity summary') + ->assertSee('Health summary') ->assertSee($this->patient->fullName()); } public function test_list_index_does_not_auto_open_patient(): void { $this->actingAs($this->owner) - ->get(route('care.specialty.show', 'maternity')) + ->get(route('care.specialty.show', 'womens_health')) ->assertOk() - ->assertDontSee('Maternity overview'); + ->assertDontSee('Health overview'); } } diff --git a/tests/Feature/DemoSeedCommandTest.php b/tests/Feature/DemoSeedCommandTest.php index 2d388d0..64f4c6d 100644 --- a/tests/Feature/DemoSeedCommandTest.php +++ b/tests/Feature/DemoSeedCommandTest.php @@ -447,7 +447,9 @@ class DemoSeedCommandTest extends TestCase $this->assertSame((int) $member->branch_id, (int) $linked->first()->branch_id); $modules = app(\App\Services\Care\SpecialtyModuleService::class); - $this->assertTrue($modules->isDeskSpecialist($org, $member)); + // Dedicated specialist roles (dentist) use primary apps, not legacy desk-specialist matching. + $this->assertFalse($modules->isDeskSpecialist($org, $member)); + $this->assertSame(['dentistry'], app(\App\Services\Care\CarePermissions::class)->primaryAppsFor($member)); $keys = collect($modules->enabledModulesForMember($org, $member))->pluck('key')->all(); $this->assertSame(['dentistry'], $keys); }