Redesign Care patient-flow board and remove Queue from nurses.
Deploy Ladill Care / deploy (push) Successful in 39s

Shared queue-board is now a Waiting/Called/In care/Done stage board with prominent tickets and Call next; nurses lose queue.manage and canAccessPatientQueue so /queue and queue nav are gated while specialty workspaces stay available.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 19:42:44 +00:00
co-authored by Cursor
parent 131ccd6edb
commit 93c7a71ee5
28 changed files with 511 additions and 161 deletions
@@ -176,7 +176,8 @@ class ConsultationController extends Controller
$queueIntegration = ['enabled' => false];
$queueBranchId = null;
$queueBridge = app(CareQueueBridge::class);
if ($permissions->handlesFloorCare($member) && $queueBridge->isEnabled($organization)) {
$canAccessPatientQueue = $permissions->canAccessPatientQueue($member);
if ($canAccessPatientQueue && $queueBridge->isEnabled($organization)) {
$queueBranchId = app(BranchContext::class)->resolve($request, $organization, $member)
?: (int) ($consultation->practitioner?->branch_id
?: $consultation->visit?->branch_id
@@ -207,6 +208,7 @@ class ConsultationController extends Controller
'consultationAssessments' => $consultationAssessments,
'startableTemplates' => $startableTemplates,
'assessmentStatuses' => config('care.assessment_statuses'),
'canAccessPatientQueue' => $canAccessPatientQueue,
'universalTemplate' => $universalTemplate,
'universalAssessment' => $universalAssessment,
'canCaptureUniversal' => $canCaptureUniversal,
@@ -41,7 +41,9 @@ class DashboardController extends Controller
$canBills = $this->permissions->can($member, 'bills.view');
$canFinance = $this->permissions->can($member, 'reports.finance.view');
$canConsult = $this->permissions->can($member, 'consultations.manage');
$showPatientQueue = $this->permissions->can($member, 'appointments.view') && ! $canBranches;
$showPatientQueue = $this->permissions->canAccessPatientQueue($member)
&& $this->permissions->can($member, 'appointments.view')
&& ! $canBranches;
$branchQuery = Branch::owned($owner)->where('organization_id', $organization->id);
$this->scopeToBranch($request, $branchQuery);
+21 -2
View File
@@ -32,7 +32,7 @@ class QueueController extends Controller
public function index(Request $request): View
{
$this->authorizeAbility($request, 'appointments.view');
abort_unless(app(CarePermissions::class)->handlesFloorCare($this->member($request)), 403);
abort_unless(app(CarePermissions::class)->canAccessPatientQueue($this->member($request)), 403);
$organization = $this->organization($request);
$member = $this->member($request);
$owner = $this->ownerRef($request);
@@ -126,6 +126,24 @@ class QueueController extends Controller
$todayQuery->where('branch_id', $branchId);
}
$doneQuery = Appointment::owned($owner)
->where('organization_id', $organization->id)
->where('status', Appointment::STATUS_COMPLETED)
->whereDate('completed_at', today());
if ($lockToPractitioner) {
$doneQuery->whereIn('practitioner_id', $practitionerScope ?: [0]);
} elseif ($branchId) {
$doneQuery->where('branch_id', $branchId);
}
if ($practitionerId && ! $lockToPractitioner) {
$doneQuery->where('practitioner_id', $practitionerId);
}
$done = $doneQuery
->with(['patient', 'practitioner'])
->orderByDesc('completed_at')
->limit(20)
->get();
$heroStats = [
'waiting' => $queue->count(),
'in_consultation' => $inConsultation->count(),
@@ -149,6 +167,7 @@ class QueueController extends Controller
'practitionerId' => $practitionerId,
'queue' => $queue,
'inConsultation' => $inConsultation,
'done' => $done,
'canManageQueue' => $canManageQueue,
'canConsult' => $canConsult,
'heroStats' => $heroStats,
@@ -160,7 +179,7 @@ class QueueController extends Controller
public function callNext(Request $request, CareQueueSessionAdvance $advance): RedirectResponse
{
abort_unless(app(CarePermissions::class)->handlesFloorCare($this->member($request)), 403);
abort_unless(app(CarePermissions::class)->canAccessPatientQueue($this->member($request)), 403);
$this->authorizeAbility($request, 'appointments.view');
$organization = $this->organization($request);
abort_unless($this->queueBridge->isEnabled($organization), 404);
@@ -39,8 +39,12 @@ class SpecialtyModuleController extends Controller
403,
);
// Specialty patient flow lives on Queue; clinical depth opens from Queue Start.
return redirect()->route('care.queue.index');
// Specialty patient flow lives on Queue; nurses stay on the specialty workspace.
if (app(\App\Services\Care\CarePermissions::class)->canAccessPatientQueue($this->member($request))) {
return redirect()->route('care.queue.index');
}
return redirect()->route('care.specialty.workspace', $module);
}
public function visits(
@@ -57,7 +61,11 @@ class SpecialtyModuleController extends Controller
403,
);
return redirect()->route('care.queue.index');
if (app(\App\Services\Care\CarePermissions::class)->canAccessPatientQueue($this->member($request))) {
return redirect()->route('care.queue.index');
}
return redirect()->route('care.specialty.workspace', $module);
}
public function history(
@@ -543,7 +551,14 @@ class SpecialtyModuleController extends Controller
);
return redirect()
->route('care.queue.index')
->route(
app(\App\Services\Care\CarePermissions::class)->canAccessPatientQueue($member)
? 'care.queue.index'
: 'care.specialty.workspace',
app(\App\Services\Care\CarePermissions::class)->canAccessPatientQueue($member)
? []
: ['module' => $module],
)
->with('success', 'Referred '.$patient->fullName().' to '.($definition['label'] ?? $module).'.');
}
@@ -650,6 +665,7 @@ class SpecialtyModuleController extends Controller
|| $permissions->can($member, 'consultations.manage')
);
$canManageQueue = $permissions->can($member, 'appointments.manage') && $canManageSpecialty;
$canAccessPatientQueue = $permissions->canAccessPatientQueue($member);
$canBookAppointments = $permissions->can($member, 'appointments.manage');
$canViewPatients = $permissions->can($member, 'patients.view');
$canViewAppointments = $permissions->can($member, 'appointments.view');
@@ -657,6 +673,22 @@ class SpecialtyModuleController extends Controller
? (string) (\App\Models\Branch::owned($owner)->whereKey($branchId)->value('name') ?? '')
: '';
$done = Appointment::owned($owner)
->where('organization_id', $organization->id)
->where('status', Appointment::STATUS_COMPLETED)
->whereDate('completed_at', today())
->when($departmentIds !== [], fn ($q) => $q->whereIn('department_id', $departmentIds))
->when($departmentIds === [], fn ($q) => $q->whereRaw('1 = 0'))
->when($kpiBranch, fn ($q) => $q->where('branch_id', $kpiBranch))
->when(
$practitionerScope !== null,
fn ($q) => $q->whereIn('practitioner_id', $practitionerScope ?: [0]),
)
->with(['patient', 'practitioner'])
->orderByDesc('completed_at')
->limit(20)
->get();
$workspaceVisit = null;
$patientHeader = null;
$timeline = [];
@@ -843,6 +875,7 @@ class SpecialtyModuleController extends Controller
'waiting' => $waiting,
'queue' => $waiting,
'inConsultation' => $inConsultation,
'done' => $done,
'openVisits' => $openVisits,
'visitHistory' => $visitHistory,
'kpis' => $kpis,
@@ -900,6 +933,7 @@ class SpecialtyModuleController extends Controller
'canManageClinical' => $canManageClinical,
'canManageVitals' => $canManageVitals,
'canManageQueue' => $canManageQueue,
'canAccessPatientQueue' => $canAccessPatientQueue,
'canBookAppointments' => $canBookAppointments,
'canViewPatients' => $canViewPatients,
'canViewAppointments' => $canViewAppointments,
+14 -1
View File
@@ -34,7 +34,7 @@ class CarePermissions
],
'nurse' => [
'dashboard.view', 'patients.view', 'appointments.view',
'consultations.view', 'vitals.manage', 'queue.manage',
'consultations.view', 'vitals.manage',
'service_queues.console',
'assessments.view', 'assessments.capture',
],
@@ -109,4 +109,17 @@ class CarePermissions
'cashier',
], true);
}
/**
* Dedicated patient-flow board (GP / specialty Queue homes).
* Nurses keep specialty workspaces and vitals not the call-next board.
*/
public function canAccessPatientQueue(?Member $member): bool
{
if (! $this->handlesFloorCare($member)) {
return false;
}
return $member->role !== 'nurse';
}
}