$diagnoses * @return Collection */ public function suggest(Patient $patient, iterable $diagnoses): Collection { $activeIds = $this->activeFor($patient)->pluck('pathway_id')->all(); return $this->matcher->match($diagnoses)->map(function (array $row) use ($activeIds) { $row['already_active'] = in_array($row['pathway']->id, $activeIds, true); return $row; }); } /** * @param array{ * consultation?: ?Consultation, * activation_diagnosis_text?: ?string, * actor?: ?string, * notes?: ?string, * } $context */ public function activate( Patient $patient, ClinicalPathway $pathway, string $ownerRef, ?Member $member, array $context = [], ): PatientPathway { if (! $this->permissions->can($member, 'pathways.manage')) { throw new HttpException(403, 'Not authorized to manage clinical pathways.'); } if (! $pathway->is_active) { throw ValidationException::withMessages([ 'pathway_code' => 'Pathway is inactive.', ]); } $consultation = $context['consultation'] ?? null; $actor = $context['actor'] ?? null; $diagnosisText = $context['activation_diagnosis_text'] ?? $this->snapshotDiagnosisText($consultation); return DB::transaction(function () use ($patient, $pathway, $ownerRef, $member, $consultation, $actor, $diagnosisText, $context) { $existingActive = PatientPathway::query() ->where('patient_id', $patient->id) ->where('pathway_id', $pathway->id) ->where('status', PatientPathway::STATUS_ACTIVE) ->lockForUpdate() ->first(); if ($existingActive) { return $existingActive->load(['pathway.templates', 'assessments.template']); } $patientPathway = PatientPathway::create([ 'owner_ref' => $ownerRef, 'organization_id' => $patient->organization_id, 'patient_id' => $patient->id, 'pathway_id' => $pathway->id, 'status' => PatientPathway::STATUS_ACTIVE, 'activated_at' => now(), 'activated_by' => $actor, 'activation_consultation_id' => $consultation?->id, 'activation_diagnosis_text' => $diagnosisText ? mb_substr($diagnosisText, 0, 500) : null, 'notes' => $context['notes'] ?? null, ]); $pathway->loadMissing('templates'); foreach ($pathway->templates->where('is_required_on_activation', true) as $binding) { try { $this->assessments->start( $patient, $binding->template_code, $ownerRef, $member, [ 'consultation' => $consultation, 'visit' => $consultation?->visit, 'patient_pathway' => $patientPathway, 'actor' => $actor, ], ); } catch (ValidationException $e) { // Template may not be seeded yet (content pack PR); skip missing packs. if (isset($e->errors()['template_code'])) { continue; } throw $e; } } AuditLogger::record( $ownerRef, 'pathway.activated', $patient->organization_id, $actor, PatientPathway::class, $patientPathway->id, [ 'pathway_code' => $pathway->code, 'activation_diagnosis_text' => $patientPathway->activation_diagnosis_text, ], ); return $patientPathway->load(['pathway.templates', 'assessments.template']); }); } public function deactivate( PatientPathway $patientPathway, string $ownerRef, ?Member $member, ?string $actorRef = null, ): PatientPathway { if (! $this->permissions->can($member, 'pathways.manage')) { throw new HttpException(403, 'Not authorized to manage clinical pathways.'); } if ($patientPathway->owner_ref !== $ownerRef) { throw new HttpException(404, 'Pathway enrollment not found.'); } if ($patientPathway->status !== PatientPathway::STATUS_ACTIVE) { return $patientPathway; } $patientPathway->update([ 'status' => PatientPathway::STATUS_INACTIVE, 'resolved_at' => now(), ]); AuditLogger::record( $ownerRef, 'pathway.deactivated', $patientPathway->organization_id, $actorRef, PatientPathway::class, $patientPathway->id, ['pathway_code' => $patientPathway->pathway?->code ?? $patientPathway->pathway_id], ); return $patientPathway->fresh(['pathway']); } /** * @return Collection */ public function activeFor(Patient $patient): Collection { return PatientPathway::query() ->where('patient_id', $patient->id) ->where('status', PatientPathway::STATUS_ACTIVE) ->with('pathway') ->orderByDesc('activated_at') ->get(); } protected function snapshotDiagnosisText(?Consultation $consultation): ?string { if (! $consultation) { return null; } $consultation->loadMissing('diagnoses'); if ($consultation->diagnoses->isEmpty()) { return null; } return $consultation->diagnoses ->map(fn ($d) => trim(($d->code ? $d->code.' ' : '').$d->description)) ->filter() ->implode('; '); } }