assertEngineEnabled($request); $this->authorizeAbility($request, 'assessments.view'); $this->authorizePatient($request, $patient); $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request)); $page = $this->assessments->listForPatient( $patient, $this->ownerRef($request), $request->only(['status', 'template_code', 'category', 'from', 'to', 'per_page']), $branchScope, ); return response()->json($page); } public function templates(Request $request): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeAbility($request, 'assessments.view'); $templates = AssessmentTemplate::query() ->system() ->current() ->with('questions') ->orderBy('category') ->orderBy('name') ->get(); return response()->json(['data' => $templates]); } public function store(Request $request, Patient $patient): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeCapture($request); $this->authorizePatient($request, $patient); $validated = $request->validate([ 'template_code' => ['required', 'string', 'max:100'], 'consultation_uuid' => ['nullable', 'uuid'], 'visit_uuid' => ['nullable', 'uuid'], ]); $context = $this->resolveContext($request, $patient, $validated); $assessment = $this->assessments->start( $patient, $validated['template_code'], $this->ownerRef($request), $this->member($request), $context, ); $status = $assessment->wasRecentlyCreated ? 201 : 200; return response()->json($this->serializeAssessment($assessment), $status); } public function storeForConsultation(Request $request, Consultation $consultation): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeCapture($request); $this->authorizeConsultation($request, $consultation); $consultation->loadMissing(['patient', 'visit']); $this->authorizePatient($request, $consultation->patient); $validated = $request->validate([ 'template_code' => ['required', 'string', 'max:100'], ]); $assessment = $this->assessments->start( $consultation->patient, $validated['template_code'], $this->ownerRef($request), $this->member($request), [ 'consultation' => $consultation, 'visit' => $consultation->visit, 'actor' => $this->ownerRef($request), ], ); return response()->json($this->serializeAssessment($assessment), $assessment->wasRecentlyCreated ? 201 : 200); } public function show(Request $request, Assessment $assessment): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeAbility($request, 'assessments.view'); $this->authorizeAssessment($request, $assessment); return response()->json($this->serializeAssessment( $assessment->load(['template.questions', 'answers.question', 'score', 'patient', 'consultation', 'visit']) )); } public function update(Request $request, Assessment $assessment): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeCapture($request); $this->authorizeAssessment($request, $assessment); $validated = $request->validate([ 'answers' => ['nullable', 'array'], 'notes' => ['nullable', 'string', 'max:10000'], ]); $updated = $this->assessments->saveAnswers( $assessment, $this->ownerRef($request), $validated['answers'] ?? [], $this->member($request), $this->ownerRef($request), array_key_exists('notes', $validated) ? $validated['notes'] : null, ); return response()->json($this->serializeAssessment($updated)); } public function complete(Request $request, Assessment $assessment): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeCapture($request); $this->authorizeAssessment($request, $assessment); if ($request->has('answers') || $request->has('notes')) { $validated = $request->validate([ 'answers' => ['nullable', 'array'], 'notes' => ['nullable', 'string', 'max:10000'], ]); $this->assessments->saveAnswers( $assessment, $this->ownerRef($request), $validated['answers'] ?? [], $this->member($request), $this->ownerRef($request), array_key_exists('notes', $validated) ? $validated['notes'] : null, ); $assessment->refresh(); } $completed = $this->assessments->complete( $assessment, $this->ownerRef($request), $this->member($request), $this->ownerRef($request), ); return response()->json($this->serializeAssessment($completed)); } public function cancel(Request $request, Assessment $assessment): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeCapture($request); $this->authorizeAssessment($request, $assessment); $cancelled = $this->assessments->cancel( $assessment, $this->ownerRef($request), $this->member($request), $this->ownerRef($request), ); return response()->json($this->serializeAssessment($cancelled)); } public function fhir(Request $request, Assessment $assessment): JsonResponse { $this->assertEngineEnabled($request); $this->authorizeAbility($request, 'assessments.view'); $this->authorizeAssessment($request, $assessment); return response()->json( $this->fhir->exportBundle($assessment), 200, ['Content-Type' => 'application/fhir+json'], ); } protected function serializeAssessment(Assessment $assessment): array { $assessment->loadMissing(['template', 'answers.question', 'score', 'patient']); $answers = []; foreach ($assessment->answers as $answer) { if ($answer->question) { $answers[$answer->question->code] = $answer->authoritativeValue($answer->question->answer_type); } } return [ 'uuid' => $assessment->uuid, 'status' => $assessment->status, 'template_code' => $assessment->template?->code, 'template_name' => $assessment->template?->name, 'template_version' => $assessment->template?->version, 'patient_uuid' => $assessment->patient?->uuid, 'consultation_uuid' => $assessment->consultation?->uuid, 'visit_uuid' => $assessment->visit?->uuid, 'assessed_at' => $assessment->assessed_at?->toIso8601String(), 'completed_at' => $assessment->completed_at?->toIso8601String(), 'notes' => $assessment->notes, 'answers' => $answers, 'score' => $assessment->score ? [ 'total_score' => $assessment->score->total_score, 'max_score' => $assessment->score->max_score, 'subscores' => $assessment->score->subscores, 'severity_label' => $assessment->score->severity_label, 'computed_at' => $assessment->score->computed_at?->toIso8601String(), ] : null, 'template' => $assessment->template, ]; } protected function assertEngineEnabled(Request $request): void { abort_unless( $this->features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE), 404, ); $this->features->ensureAssessmentCatalog(); } protected function authorizeCapture(Request $request): void { $member = $this->member($request); abort_unless( $this->permissions->can($member, 'assessments.capture') || $this->permissions->can($member, 'assessments.manage'), 403, ); } protected function authorizePatient(Request $request, Patient $patient): void { $this->authorizeOwner($request, $patient); abort_unless($patient->organization_id === $this->organization($request)->id, 404); $branchId = app(OrganizationResolver::class)->branchScope($this->member($request)); if ($branchId !== null && $patient->branch_id !== $branchId) { abort(404); } } protected function authorizeConsultation(Request $request, Consultation $consultation): void { $this->authorizeOwner($request, $consultation); $consultation->loadMissing('visit'); abort_unless($consultation->visit->organization_id === $this->organization($request)->id, 404); $branchId = app(OrganizationResolver::class)->branchScope($this->member($request)); if ($branchId !== null && $consultation->visit->branch_id !== $branchId) { abort(404); } } protected function authorizeAssessment(Request $request, Assessment $assessment): void { $this->authorizeOwner($request, $assessment); abort_unless($assessment->organization_id === $this->organization($request)->id, 404); $branchId = app(OrganizationResolver::class)->branchScope($this->member($request)); if ($branchId !== null && $assessment->branch_id !== $branchId) { abort(404); } } /** * @param array{consultation_uuid?: ?string, visit_uuid?: ?string} $validated * @return array{consultation?: Consultation, visit?: Visit, actor: string} */ protected function resolveContext(Request $request, Patient $patient, array $validated): array { $context = ['actor' => $this->ownerRef($request)]; if (! empty($validated['consultation_uuid'])) { $consultation = Consultation::query() ->where('uuid', $validated['consultation_uuid']) ->where('patient_id', $patient->id) ->firstOrFail(); $this->authorizeConsultation($request, $consultation); $consultation->loadMissing('visit'); $context['consultation'] = $consultation; $context['visit'] = $consultation->visit; } elseif (! empty($validated['visit_uuid'])) { $visit = Visit::query() ->where('uuid', $validated['visit_uuid']) ->where('patient_id', $patient->id) ->firstOrFail(); $this->authorizeOwner($request, $visit); abort_unless($visit->organization_id === $this->organization($request)->id, 404); $branchId = app(OrganizationResolver::class)->branchScope($this->member($request)); if ($branchId !== null && $visit->branch_id !== $branchId) { abort(404); } $context['visit'] = $visit; } return $context; } }