diff --git a/app/Http/Controllers/Api/AssessmentController.php b/app/Http/Controllers/Api/AssessmentController.php index 77bbeda..f7db834 100644 --- a/app/Http/Controllers/Api/AssessmentController.php +++ b/app/Http/Controllers/Api/AssessmentController.php @@ -250,6 +250,7 @@ class AssessmentController extends Controller $this->features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE), 404, ); + $this->features->ensureAssessmentCatalog(); } protected function authorizeCapture(Request $request): void diff --git a/app/Http/Controllers/Api/PathwayController.php b/app/Http/Controllers/Api/PathwayController.php index 52cbc0a..7256dbc 100644 --- a/app/Http/Controllers/Api/PathwayController.php +++ b/app/Http/Controllers/Api/PathwayController.php @@ -164,6 +164,7 @@ class PathwayController extends Controller $this->features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE), 404, ); + $this->features->ensureAssessmentCatalog(); } protected function authorizePatient(Request $request, Patient $patient): void diff --git a/app/Http/Controllers/Care/AssessmentController.php b/app/Http/Controllers/Care/AssessmentController.php index adf66d8..2c9dc8b 100644 --- a/app/Http/Controllers/Care/AssessmentController.php +++ b/app/Http/Controllers/Care/AssessmentController.php @@ -294,6 +294,7 @@ class AssessmentController extends Controller $this->features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE), 404, ); + $this->features->ensureAssessmentCatalog(); } protected function authorizeCapture(Request $request): void diff --git a/app/Http/Controllers/Care/ConsultationController.php b/app/Http/Controllers/Care/ConsultationController.php index 9b6ae29..f526bd8 100644 --- a/app/Http/Controllers/Care/ConsultationController.php +++ b/app/Http/Controllers/Care/ConsultationController.php @@ -47,7 +47,11 @@ class ConsultationController extends Controller $canPrescribe = $permissions->can($member, 'prescriptions.manage'); $canGenerateBill = $permissions->can($member, 'bills.manage'); - $assessmentsEnabled = app(CareFeatures::class)->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE); + $careFeatures = app(CareFeatures::class); + $assessmentsEnabled = $careFeatures->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE); + if ($assessmentsEnabled) { + $careFeatures->ensureAssessmentCatalog(); + } $canViewAssessments = $assessmentsEnabled && $permissions->can($member, 'assessments.view'); $canCaptureAssessment = $assessmentsEnabled && ( $permissions->can($member, 'assessments.capture') diff --git a/app/Http/Controllers/Care/OutcomeController.php b/app/Http/Controllers/Care/OutcomeController.php index 7a5e4b4..8862cb7 100644 --- a/app/Http/Controllers/Care/OutcomeController.php +++ b/app/Http/Controllers/Care/OutcomeController.php @@ -94,6 +94,7 @@ class OutcomeController extends Controller $this->features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE), 404, ); + $this->features->ensureAssessmentCatalog(); } protected function authorizePatient(Request $request, Patient $patient): void diff --git a/app/Http/Controllers/Care/PathwayController.php b/app/Http/Controllers/Care/PathwayController.php index 8254ea9..b1084bb 100644 --- a/app/Http/Controllers/Care/PathwayController.php +++ b/app/Http/Controllers/Care/PathwayController.php @@ -149,6 +149,7 @@ class PathwayController extends Controller $this->features->enabled($org, CareFeatures::ASSESSMENTS_ENGINE), 404, ); + $this->features->ensureAssessmentCatalog(); } protected function authorizePatient(Request $request, Patient $patient): void diff --git a/app/Http/Controllers/Care/PatientController.php b/app/Http/Controllers/Care/PatientController.php index ab4a807..c2cf7df 100644 --- a/app/Http/Controllers/Care/PatientController.php +++ b/app/Http/Controllers/Care/PatientController.php @@ -102,7 +102,11 @@ class PatientController extends Controller $suiteEmailReady = app(\App\Services\Billing\PlatformEmailClient::class)->isConfigured(); $suiteSmsReady = app(\App\Services\Billing\PlatformSmsClient::class)->isConfigured(); - $assessmentsEnabled = app(CareFeatures::class)->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE); + $careFeatures = app(CareFeatures::class); + $assessmentsEnabled = $careFeatures->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE); + if ($assessmentsEnabled) { + $careFeatures->ensureAssessmentCatalog(); + } $canViewAssessments = $assessmentsEnabled && $permissions->can($member, 'assessments.view'); $canCaptureAssessment = $assessmentsEnabled && ( $permissions->can($member, 'assessments.capture') diff --git a/app/Http/Controllers/Care/SettingsController.php b/app/Http/Controllers/Care/SettingsController.php index d2450fc..712c9df 100644 --- a/app/Http/Controllers/Care/SettingsController.php +++ b/app/Http/Controllers/Care/SettingsController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Care\Concerns\ScopesToAccount; use App\Models\Branch; use App\Services\Care\AppointmentPatientNotifier; use App\Services\Care\AuditLogger; +use App\Services\Care\CareFeatures; use App\Services\Care\CarePermissions; use App\Services\Care\PlanService; use App\Services\Billing\PlatformEmailClient; @@ -45,6 +46,8 @@ class SettingsController extends Controller $suiteEmailReady = $platformEmail->isConfigured(); $suiteSmsReady = $platformSms->isConfigured(); + $careFeatures = app(CareFeatures::class); + return view('care.settings.edit', [ 'organization' => $organization, 'canManage' => $canManage, @@ -53,6 +56,7 @@ class SettingsController extends Controller 'canViewTeam' => $permissions->can($member, 'admin.members.view'), 'hasBranchesFeature' => $plans->hasFeature($organization, 'branches'), 'canUseQueueIntegration' => $plans->canUseQueueIntegration($organization), + 'assessmentsEngineEnabled' => $careFeatures->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE), 'messagingSmsReady' => $suiteSmsReady || $credential->hasValidSms(), 'messagingEmailReady' => $suiteEmailReady || $credential->hasValidBird(), 'suiteMessagingReady' => $suiteEmailReady || $suiteSmsReady, @@ -82,6 +86,7 @@ class SettingsController extends Controller 'logo' => ['nullable', 'image', 'mimes:jpeg,png,jpg,webp,svg', 'max:2048'], 'remove_logo' => ['nullable', 'boolean'], 'queue_integration_enabled' => ['nullable', 'boolean'], + 'assessments_engine' => ['nullable', 'boolean'], 'notify_appointment_booked_sms' => ['nullable', 'boolean'], 'notify_appointment_booked_email' => ['nullable', 'boolean'], 'notify_video_scheduled_sms' => ['nullable', 'boolean'], @@ -92,6 +97,14 @@ class SettingsController extends Controller $settings['onboarded'] = true; $settings['facility_type'] = $validated['facility_type']; + $rollout = is_array($settings['rollout'] ?? null) ? $settings['rollout'] : []; + // Explicit save so opt-out is stored (engine defaults ON when unset). + $rollout[CareFeatures::ASSESSMENTS_ENGINE] = $request->boolean('assessments_engine'); + $settings['rollout'] = $rollout; + if ($rollout[CareFeatures::ASSESSMENTS_ENGINE]) { + app(CareFeatures::class)->ensureAssessmentCatalog(); + } + // Queue integration is Pro/Enterprise only — free plans cannot enable it. $wantsQueue = $request->boolean('queue_integration_enabled'); if ($wantsQueue && ! $plans->canUseQueueIntegration($organization)) { diff --git a/app/Services/Care/CareFeatures.php b/app/Services/Care/CareFeatures.php index bf05988..a90a644 100644 --- a/app/Services/Care/CareFeatures.php +++ b/app/Services/Care/CareFeatures.php @@ -2,7 +2,12 @@ namespace App\Services\Care; +use App\Models\AssessmentTemplate; use App\Models\Organization; +use Database\Seeders\AssessmentTemplateSeeder; +use Database\Seeders\ClinicalPathwaySeeder; +use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Schema; /** * Product rollout flags stored under organization settings.rollout.* @@ -16,12 +21,25 @@ class CareFeatures public const ASSESSMENT_REQUIRED_ON_COMPLETE = 'assessment_required_on_complete'; + /** + * Flags that default ON when unset (opt-out). Others default OFF (opt-in). + * + * @var list + */ + protected array $defaultOn = [ + self::ASSESSMENTS_ENGINE, + ]; + public function enabled(Organization $organization, string $flag): bool { $settings = $organization->settings ?? []; $rollout = is_array($settings['rollout'] ?? null) ? $settings['rollout'] : []; - return (bool) ($rollout[$flag] ?? false); + if (array_key_exists($flag, $rollout)) { + return (bool) $rollout[$flag]; + } + + return in_array($flag, $this->defaultOn, true); } /** @@ -37,4 +55,31 @@ class CareFeatures return $organization->fresh(); } + + /** + * Ensure platform assessment/pathway catalog exists (idempotent seed). + * Call when the engine is enabled so consult/patient UI has templates. + */ + public function ensureAssessmentCatalog(): void + { + if (! Schema::hasTable('care_assessment_templates')) { + return; + } + + if (AssessmentTemplate::query()->whereNull('organization_id')->exists()) { + return; + } + + Artisan::call('db:seed', [ + '--class' => AssessmentTemplateSeeder::class, + '--force' => true, + ]); + + if (Schema::hasTable('care_clinical_pathways')) { + Artisan::call('db:seed', [ + '--class' => ClinicalPathwaySeeder::class, + '--force' => true, + ]); + } + } } diff --git a/resources/views/care/patients/index.blade.php b/resources/views/care/patients/index.blade.php index 19afd9c..6669bba 100644 --- a/resources/views/care/patients/index.blade.php +++ b/resources/views/care/patients/index.blade.php @@ -23,13 +23,24 @@ @endif + @if (request('focus') === 'assessments') +
+

Clinical assessments

+

+ Open a patient chart for assessments, pathways, and outcomes — or start a visit from + Queue + and use Universal intake / Clinical pathways on the consultation. +

+
+ @endif +
- @if (request()->hasAny(['q', 'date_of_birth', 'phone', 'national_id', 'patient_number'])) + @if (request()->hasAny(['q', 'date_of_birth', 'phone', 'national_id', 'patient_number', 'focus'])) Clear @endif
diff --git a/resources/views/care/settings/edit.blade.php b/resources/views/care/settings/edit.blade.php index e00f3c7..f88c9e5 100644 --- a/resources/views/care/settings/edit.blade.php +++ b/resources/views/care/settings/edit.blade.php @@ -89,6 +89,21 @@ @endif @if ($canManage) + + + +

Set a Ladill mailbox (From address) and optional SMS sender ID under Account → Messaging. diff --git a/resources/views/partials/sidebar.blade.php b/resources/views/partials/sidebar.blade.php index 7c6c18a..cb66099 100644 --- a/resources/views/partials/sidebar.blade.php +++ b/resources/views/partials/sidebar.blade.php @@ -30,6 +30,23 @@ 'icon' => '']; } + // Clinical assessments (layered intake / pathways) — on by default; toggle in Settings. + $assessmentsEngineOn = $organization + && app(\App\Services\Care\CareFeatures::class)->enabled( + $organization, + \App\Services\Care\CareFeatures::ASSESSMENTS_ENGINE, + ); + if ($assessmentsEngineOn && $permissions->can($member, 'assessments.view') && $permissions->can($member, 'patients.view')) { + $nav[] = [ + 'name' => 'Assessments', + 'route' => route('care.patients.index', ['focus' => 'assessments']), + 'active' => request()->routeIs('care.assessments.*') + || request()->routeIs('care.pathways.*') + || request()->routeIs('care.outcomes.*'), + 'icon' => '', + ]; + } + // Lab, pharmacy, billing, and Queue service counters are Pro/Enterprise modules. if (! empty($hasPaidPlan)) { if ($permissions->can($member, 'lab.view')) { diff --git a/tests/Feature/CareAssessmentApiTest.php b/tests/Feature/CareAssessmentApiTest.php index 57e39c3..a4f801d 100644 --- a/tests/Feature/CareAssessmentApiTest.php +++ b/tests/Feature/CareAssessmentApiTest.php @@ -91,6 +91,7 @@ class CareAssessmentApiTest extends TestCase public function test_api_lists_templates_and_404_when_flag_off(): void { + // Engine defaults ON when rollout flag is unset. $this->getJson('/api/v1/assessment-templates') ->assertOk() ->assertJsonPath('data.0.code', fn ($c) => is_string($c)); @@ -100,6 +101,7 @@ class CareAssessmentApiTest extends TestCase 'rollout' => [CareFeatures::ASSESSMENTS_ENGINE => false], ]), ]); + $this->organization->refresh(); $this->getJson('/api/v1/assessment-templates')->assertNotFound(); }