Files
ladill-care/app/Http/Controllers/Care/PathwayController.php
T
isaaccladandCursor 94e53d05bf
Deploy Ladill Care / deploy (push) Successful in 1m7s
Keep consultation context on nested clinical pages and add Call again on patient queue cards.
Doctors can return to the active consultation from forms, lab, prescriptions, pathways, and bills; queue cards can re-announce called/serving tickets via the existing Queue recall bridge.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 17:48:34 +00:00

185 lines
6.8 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\ConsultationReturnContext;
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'),
'returnConsultation' => app(ConsultationReturnContext::class)->resolve($request, (int) $patient->id),
]);
}
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 = app(ConsultationReturnContext::class);
return redirect()
->route('care.pathways.index', array_filter([
'patient' => $patient,
...$return->routeQuery($request),
]))
->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,
);
$this->features->ensureAssessmentCatalog();
}
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);
}
}
}