Gate specialty modules by staff role with view and refer levels.
Deploy Ladill Care / deploy (push) Successful in 37s

Give nurses, lab techs, pharmacists, and GPs broad access to day-to-day modules, keep restricted specialties for specialists (with support nurses), and let GPs view results or refer without full clinical edit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 18:03:47 +00:00
co-authored by Cursor
parent 7989ab9184
commit 2cff8fdd6c
13 changed files with 762 additions and 89 deletions
@@ -104,6 +104,7 @@ class SpecialtyModuleController extends Controller
$organization = $this->organization($request);
$member = $this->member($request);
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
abort_unless($visit->organization_id === $organization->id, 404);
$validated = $request->validate([
@@ -142,6 +143,7 @@ class SpecialtyModuleController extends Controller
$organization = $this->organization($request);
$member = $this->member($request);
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
abort_unless($visit->organization_id === $organization->id, 404);
$this->authorizeBranch($request, (int) $visit->branch_id);
@@ -256,6 +258,7 @@ class SpecialtyModuleController extends Controller
$organization = $this->organization($request);
$member = $this->member($request);
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
abort_unless($visit->organization_id === $organization->id, 404);
$tab = (string) $request->input('tab', '');
@@ -345,6 +348,7 @@ class SpecialtyModuleController extends Controller
$organization = $this->organization($request);
$member = $this->member($request);
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
abort_unless($visit->organization_id === $organization->id, 404);
abort_unless($visit->patient_id, 422);
@@ -388,6 +392,7 @@ class SpecialtyModuleController extends Controller
$member = $this->member($request);
$owner = $this->ownerRef($request);
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
abort_unless($queueBridge->isEnabled($organization), 404);
$resolver = app(OrganizationResolver::class);
@@ -473,6 +478,74 @@ class SpecialtyModuleController extends Controller
return back()->with('success', 'Called '.$ticket['ticket_number'].' — '.$name.'.');
}
/**
* Refer a patient into this specialty queue (walk-in) without full clinical edit access.
*/
public function refer(
Request $request,
string $module,
SpecialtyModuleService $modules,
AppointmentService $appointments,
): RedirectResponse {
$definition = $modules->definition($module);
abort_unless($definition, 404);
$organization = $this->organization($request);
$member = $this->member($request);
abort_unless($modules->memberCanRefer($organization, $member, $module), 403);
$owner = $this->ownerRef($request);
$branchScope = app(OrganizationResolver::class)->branchScope($member);
$validated = $request->validate([
'patient_id' => ['required', 'integer', 'exists:care_patients,id'],
'branch_id' => ['nullable', 'integer', 'exists:care_branches,id'],
'reason' => ['nullable', 'string', 'max:500'],
'notes' => ['nullable', 'string', 'max:2000'],
]);
$branchId = (int) ($validated['branch_id'] ?? $branchScope ?? 0);
abort_unless($branchId > 0, 422);
$this->authorizeBranch($request, $branchId);
$department = Department::owned($owner)
->where('type', $definition['department_type'] ?? 'general')
->where('branch_id', $branchId)
->where('is_active', true)
->orderBy('id')
->first();
if (! $department) {
return back()->with('error', 'No active '.($definition['label'] ?? $module).' department at this branch.');
}
$patient = \App\Models\Patient::owned($owner)
->where('organization_id', $organization->id)
->findOrFail((int) $validated['patient_id']);
$appointment = $appointments->walkIn($organization, $owner, [
'patient_id' => $patient->id,
'branch_id' => $branchId,
'department_id' => $department->id,
'reason' => $validated['reason'] ?? ('Referral to '.($definition['label'] ?? $module)),
'notes' => $validated['notes'] ?? null,
], $owner);
\App\Services\Care\AuditLogger::record(
$owner,
'specialty.referral',
$organization->id,
$owner,
Appointment::class,
$appointment->id,
['module' => $module, 'department_id' => $department->id],
);
return redirect()
->route('care.queue.index')
->with('success', 'Referred '.$patient->fullName().' to '.($definition['label'] ?? $module).'.');
}
protected function renderShell(
Request $request,
string $module,
@@ -489,6 +562,10 @@ class SpecialtyModuleController extends Controller
$member = $this->member($request);
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
$canManageSpecialty = $modules->memberCanManage($organization, $member, $module);
$canReferSpecialty = $modules->memberCanRefer($organization, $member, $module);
$specialtyAccessLevel = $modules->memberAccessLevel($organization, $member, $module);
$owner = $this->ownerRef($request);
$resolver = app(OrganizationResolver::class);
$branchScope = $resolver->branchScope($member);
@@ -559,8 +636,8 @@ class SpecialtyModuleController extends Controller
$services = $shell->provisionedServices($organization, $module);
$permissions = app(\App\Services\Care\CarePermissions::class);
$canConsult = $permissions->can($member, 'consultations.manage');
$canManageQueue = $permissions->can($member, 'appointments.manage');
$canConsult = $permissions->can($member, 'consultations.manage') && $canManageSpecialty;
$canManageQueue = $permissions->can($member, 'appointments.manage') && $canManageSpecialty;
$branchLabel = $branchId
? (string) (\App\Models\Branch::owned($owner)->whereKey($branchId)->value('name') ?? '')
: '';
@@ -803,6 +880,9 @@ class SpecialtyModuleController extends Controller
'lockToPractitioner' => $practitionerScope !== null,
'canConsult' => $canConsult,
'canManageQueue' => $canManageQueue,
'canManageSpecialty' => $canManageSpecialty,
'canReferSpecialty' => $canReferSpecialty,
'specialtyAccessLevel' => $specialtyAccessLevel,
'queueIntegration' => $branchId
? $queueBridge->listMeta($organization, $module, $branchId)
: ['enabled' => false],