Files
ladill-care/app/Http/Controllers/Care/PathwayController.php
T
isaacclad 2ce4bc8993
Deploy Ladill Care / deploy (push) Successful in 1m26s
feat(assessments): layered clinical assessment engine end-to-end
Add a template-driven assessment system with universal intake, clinical
pathways, disease instruments (stroke MVP + extended, diabetes, and ten
specialty packs), scoring, patient outcome trends, REST/API + FHIR
export, and Enterprise org-level assessment analytics. Seed packs and
design/licensing docs ship for deploy and pre-GA review.
2026-07-16 22:58:09 +00:00

177 lines
6.4 KiB
PHP

<?php
namespace App\Http\Controllers\Care;
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Http\Controllers\Controller;
use App\Models\ClinicalPathway;
use App\Models\Consultation;
use App\Models\Patient;
use App\Models\PatientPathway;
use App\Services\Care\CareFeatures;
use App\Services\Care\CarePermissions;
use App\Services\Care\OrganizationResolver;
use App\Services\Care\PathwayService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PathwayController extends Controller
{
use ScopesToAccount;
public function __construct(
protected PathwayService $pathways,
protected CareFeatures $features,
protected CarePermissions $permissions,
) {}
public function index(Request $request, Patient $patient): View
{
$this->assertPathwaysEnabled($request);
$this->authorizeAbility($request, 'assessments.view');
$this->authorizePatient($request, $patient);
$active = $this->pathways->activeFor($patient);
$history = PatientPathway::query()
->owned($this->ownerRef($request))
->where('patient_id', $patient->id)
->with('pathway')
->orderByDesc('activated_at')
->paginate(20);
$catalog = ClinicalPathway::query()->active()->orderBy('sort_order')->get();
$canManage = $this->permissions->can($this->member($request), 'pathways.manage');
return view('care.pathways.index', [
'patient' => $patient,
'active' => $active,
'history' => $history,
'catalog' => $catalog,
'canManage' => $canManage,
'statuses' => config('care.patient_pathway_statuses'),
]);
}
public function store(Request $request, Patient $patient): RedirectResponse
{
$this->assertPathwaysEnabled($request);
$this->authorizeAbility($request, 'pathways.manage');
$this->authorizePatient($request, $patient);
$validated = $request->validate([
'pathway_code' => ['required', 'string', 'max:100'],
'consultation_uuid' => ['nullable', 'uuid'],
'activation_diagnosis_text' => ['nullable', 'string', 'max:500'],
]);
$pathway = ClinicalPathway::findByCode($validated['pathway_code']);
abort_unless($pathway, 422, 'Unknown or inactive pathway.');
$consultation = null;
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', 'diagnoses']);
}
$patientPathway = $this->pathways->activate(
$patient,
$pathway,
$this->ownerRef($request),
$this->member($request),
[
'consultation' => $consultation,
'activation_diagnosis_text' => $validated['activation_diagnosis_text'] ?? null,
'actor' => $this->ownerRef($request),
],
);
return redirect()
->route('care.pathways.index', $patient)
->with('success', "Pathway “{$pathway->name}” activated.");
}
public function deactivate(Request $request, Patient $patient, PatientPathway $patientPathway): RedirectResponse
{
$this->assertPathwaysEnabled($request);
$this->authorizeAbility($request, 'pathways.manage');
$this->authorizePatient($request, $patient);
abort_unless($patientPathway->patient_id === $patient->id, 404);
$this->authorizeOwner($request, $patientPathway);
$this->pathways->deactivate(
$patientPathway,
$this->ownerRef($request),
$this->member($request),
$this->ownerRef($request),
);
return back()->with('success', 'Pathway deactivated.');
}
public function suggestions(Request $request, Consultation $consultation): JsonResponse|RedirectResponse
{
$this->assertPathwaysEnabled($request);
$this->authorizeAbility($request, 'consultations.view');
$this->authorizeConsultation($request, $consultation);
$consultation->loadMissing(['diagnoses', 'patient']);
$suggestions = $this->pathways->suggest($consultation->patient, $consultation->diagnoses);
$payload = [
'data' => $suggestions->map(fn (array $row) => [
'pathway_code' => $row['pathway_code'],
'pathway_name' => $row['pathway_name'],
'rank' => $row['rank'],
'match_reason' => $row['match_reason'],
'already_active' => $row['already_active'],
])->values()->all(),
];
if ($request->wantsJson() || $request->ajax()) {
return response()->json($payload);
}
// HTML fallthrough not used — consultation show embeds suggestions server-side.
return back()->with('pathway_suggestions', $payload['data']);
}
protected function assertPathwaysEnabled(Request $request): void
{
$org = $this->organization($request);
// Pathways require assessments engine; suggestions flag is optional UX.
abort_unless(
$this->features->enabled($org, CareFeatures::ASSESSMENTS_ENGINE),
404,
);
}
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);
}
}
}