diff --git a/app/Http/Controllers/Care/ConsultationController.php b/app/Http/Controllers/Care/ConsultationController.php index d32f8fc..e5c9b4c 100644 --- a/app/Http/Controllers/Care/ConsultationController.php +++ b/app/Http/Controllers/Care/ConsultationController.php @@ -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, diff --git a/app/Http/Controllers/Care/DashboardController.php b/app/Http/Controllers/Care/DashboardController.php index aa6f894..e937b8e 100644 --- a/app/Http/Controllers/Care/DashboardController.php +++ b/app/Http/Controllers/Care/DashboardController.php @@ -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); diff --git a/app/Http/Controllers/Care/QueueController.php b/app/Http/Controllers/Care/QueueController.php index 12e236b..603aac6 100644 --- a/app/Http/Controllers/Care/QueueController.php +++ b/app/Http/Controllers/Care/QueueController.php @@ -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); diff --git a/app/Http/Controllers/Care/SpecialtyModuleController.php b/app/Http/Controllers/Care/SpecialtyModuleController.php index 81fee9d..d8e7ba4 100644 --- a/app/Http/Controllers/Care/SpecialtyModuleController.php +++ b/app/Http/Controllers/Care/SpecialtyModuleController.php @@ -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, diff --git a/app/Services/Care/CarePermissions.php b/app/Services/Care/CarePermissions.php index 2119997..d6994f8 100644 --- a/app/Services/Care/CarePermissions.php +++ b/app/Services/Care/CarePermissions.php @@ -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'; + } } diff --git a/resources/views/care/bills/index.blade.php b/resources/views/care/bills/index.blade.php index bf331c2..c553a72 100644 --- a/resources/views/care/bills/index.blade.php +++ b/resources/views/care/bills/index.blade.php @@ -30,12 +30,13 @@
- @include('care.partials.queue-ops', [ - 'queueIntegration' => $queueIntegration ?? null, - 'queueCallNextRoute' => 'care.bills.call-next', - 'queueCallNextParams' => [], - 'branchId' => $branchId ?? null, - ]) + @include('care.partials.queue-ops', [ + 'queueIntegration' => $queueIntegration ?? null, + 'queueCallNextRoute' => 'care.bills.call-next', + 'queueCallNextParams' => [], + 'branchId' => $branchId ?? null, + 'variant' => 'bar', + ])
diff --git a/resources/views/care/consultations/partials/actions-menu.blade.php b/resources/views/care/consultations/partials/actions-menu.blade.php index 44d80ef..58f1b8a 100644 --- a/resources/views/care/consultations/partials/actions-menu.blade.php +++ b/resources/views/care/consultations/partials/actions-menu.blade.php @@ -15,6 +15,7 @@ 'queueCallNextParams' => [], 'branchId' => $queueBranchId ?? null, 'currentAppointmentId' => $appointment?->id, + 'variant' => 'button', ]) @if ($ticketCalled && $appointment) @@ -71,4 +72,6 @@ @endif +@if (! empty($canAccessPatientQueue)) Back to queue +@endif diff --git a/resources/views/care/financial-obligations/index.blade.php b/resources/views/care/financial-obligations/index.blade.php index f67c0dc..0ad2bf9 100644 --- a/resources/views/care/financial-obligations/index.blade.php +++ b/resources/views/care/financial-obligations/index.blade.php @@ -13,6 +13,7 @@ 'queueCallNextRoute' => 'care.bills.call-next', 'queueCallNextParams' => [], 'branchId' => $branchId ?? null, + 'variant' => 'button', ])
@endif diff --git a/resources/views/care/lab/queue/index.blade.php b/resources/views/care/lab/queue/index.blade.php index 79f68d7..84a3937 100644 --- a/resources/views/care/lab/queue/index.blade.php +++ b/resources/views/care/lab/queue/index.blade.php @@ -35,12 +35,13 @@
- @include('care.partials.queue-ops', [ - 'queueIntegration' => $queueIntegration ?? null, - 'queueCallNextRoute' => 'care.lab.queue.call-next', - 'queueCallNextParams' => [], - 'branchId' => $branchId, - ]) + @include('care.partials.queue-ops', [ + 'queueIntegration' => $queueIntegration ?? null, + 'queueCallNextRoute' => 'care.lab.queue.call-next', + 'queueCallNextParams' => [], + 'branchId' => $branchId, + 'variant' => 'bar', + ])
diff --git a/resources/views/care/partials/queue-board-card.blade.php b/resources/views/care/partials/queue-board-card.blade.php new file mode 100644 index 0000000..6000033 --- /dev/null +++ b/resources/views/care/partials/queue-board-card.blade.php @@ -0,0 +1,86 @@ +{{-- + Single patient-flow card for queue-board stages. + + Expected: $appointment, $stage (waiting|called|in_care|done), + $canConsult, $queueIntegration, $startRouteName, $openInCareUrl (callable) +--}} +@php + $stage = $stage ?? 'waiting'; + $ticketStatus = $appointment->queue_ticket_status ?? null; + $isCalled = in_array($ticketStatus, ['called', 'serving'], true); + $openUrl = isset($openInCareUrl) ? $openInCareUrl($appointment) : null; +@endphp +
$stage === 'called' || ($stage === 'waiting' && $isCalled), + 'border-emerald-200 bg-emerald-50/60' => $stage === 'in_care', + 'border-slate-100 bg-slate-50/80' => $stage === 'waiting' && ! $isCalled, + 'border-slate-100 bg-white opacity-90' => $stage === 'done', +])> +
+ @if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_number) + @include('care.partials.queue-ticket', [ + 'ticketNumber' => $appointment->queue_ticket_number, + 'ticketStatus' => $ticketStatus, + 'destination' => $appointment->queue_destination, + 'staffDisplayName' => $appointment->queue_staff_display_name, + 'showAssignment' => $stage !== 'in_care' && $stage !== 'done', + 'prominent' => true, + ]) + @elseif (! empty($queueIntegration['enabled']) && ($appointment->queue_routing_status ?? '') === 'unresolved') + Unassigned + @elseif ($appointment->queue_position) + {{ $appointment->queue_position }} + @endif + +
+

{{ $appointment->patient?->fullName() ?? 'Patient' }}

+

+ @if ($stage === 'in_care' || $stage === 'done') + {{ $appointment->practitioner?->name ?? 'Unassigned' }} + @if ($appointment->patient?->patient_number) + · {{ $appointment->patient->patient_number }} + @endif + @else + {{ $appointment->patient?->patient_number ?? '—' }} + @if ($appointment->reason) + · {{ $appointment->reason }} + @endif + @endif +

+
+
+ +
+ @if ($stage === 'waiting' || $stage === 'called') + @if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && $isCalled && $canConsult) +
+ @csrf + +
+ @endif + @if ($canConsult) +
+ @csrf + +
+ @endif + @elseif ($stage === 'in_care') + @if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && $isCalled && $canConsult) +
+ @csrf + +
+ @endif + @if ($openUrl) + Open + @endif + @endif +
+
diff --git a/resources/views/care/partials/queue-board.blade.php b/resources/views/care/partials/queue-board.blade.php index ae9ec61..8a9920b 100644 --- a/resources/views/care/partials/queue-board.blade.php +++ b/resources/views/care/partials/queue-board.blade.php @@ -1,21 +1,22 @@ {{-- - Shared Waiting | In care board (GP Queue + specialty Queue homes). + Shared patient-flow board (GP Queue + specialty Queue homes). Expected vars: - $queue, $inConsultation (collections of Appointment) + - $done (optional — completed today) - $queueIntegration, $branchId - $canConsult (bool) - $queueCallNextRoute, $queueCallNextParams (optional — if set, Call next renders above board) - $startRouteName (default care.queue.start) — route(name, $appointment) - $openInCareUrl (callable Appointment $a): ?string — link for in-care Open - - $inCareLabel (default "In consultation") + - $inCareLabel (default "In care") - $lockToPractitioner, $canSwitchBranch, $branches, $practitioners, $practitionerId (filter bar) - $showFilters (default true) --}} @php $canConsult = $canConsult ?? false; $showFilters = $showFilters ?? true; - $inCareLabel = $inCareLabel ?? 'In consultation'; + $inCareLabel = $inCareLabel ?? 'In care'; $startRouteName = $startRouteName ?? 'care.queue.start'; $openInCareUrl = $openInCareUrl ?? function ($appointment) { return $appointment->consultation @@ -24,10 +25,58 @@ }; $queueCallNextRoute = $queueCallNextRoute ?? null; $queueCallNextParams = $queueCallNextParams ?? []; + $done = $done ?? collect(); + + $calledStatuses = ['called', 'serving']; + $waitingList = $queue->reject( + fn ($a) => in_array($a->queue_ticket_status ?? '', $calledStatuses, true) + )->values(); + $calledList = $queue->filter( + fn ($a) => in_array($a->queue_ticket_status ?? '', $calledStatuses, true) + )->values(); + + $stages = [ + [ + 'key' => 'waiting', + 'label' => 'Waiting', + 'count' => $waitingList->count(), + 'items' => $waitingList, + 'empty' => 'No patients waiting.', + 'accent' => 'text-slate-500', + 'dot' => 'bg-slate-400', + ], + [ + 'key' => 'called', + 'label' => 'Called', + 'count' => $calledList->count(), + 'items' => $calledList, + 'empty' => 'No one called yet.', + 'accent' => 'text-indigo-600', + 'dot' => 'bg-indigo-500', + ], + [ + 'key' => 'in_care', + 'label' => $inCareLabel, + 'count' => $inConsultation->count(), + 'items' => $inConsultation, + 'empty' => 'No active encounters.', + 'accent' => 'text-emerald-700', + 'dot' => 'bg-emerald-500', + ], + [ + 'key' => 'done', + 'label' => 'Done', + 'count' => $done->count(), + 'items' => $done, + 'empty' => 'None completed today.', + 'accent' => 'text-slate-400', + 'dot' => 'bg-slate-300', + ], + ]; @endphp @if ($showFilters) -
+ @if ($lockToPractitioner ?? false) @if (($canSwitchBranch ?? false) && isset($branches) && $branches->count() > 1) @elseif (isset($branches) && $branches->isNotEmpty()) - {{ $branches->first()->name }} + {{ $branches->first()->name }} @elseif (! empty($branchLabel)) - {{ $branchLabel }} + {{ $branchLabel }} @endif - Showing patients assigned to you + Assigned to you @elseif ($canSwitchBranch ?? false) - {{ $branches->first()->name }} + {{ $branches->first()->name }} @elseif (! empty($branchLabel)) - {{ $branchLabel }} + {{ $branchLabel }} @endif @if (isset($practitioners)) @endif - + @if ($variant === 'bar') + + @else + + @endif
@endif diff --git a/resources/views/care/partials/queue-ticket.blade.php b/resources/views/care/partials/queue-ticket.blade.php index 271a8da..c6bfadf 100644 --- a/resources/views/care/partials/queue-ticket.blade.php +++ b/resources/views/care/partials/queue-ticket.blade.php @@ -4,14 +4,19 @@ $destination = $destination ?? null; $staffDisplayName = $staffDisplayName ?? null; $showAssignment = ($showAssignment ?? true) && ($destination || $staffDisplayName); + $prominent = $prominent ?? false; @endphp @if ($number)
- {{ $number }} + $prominent, + 'h-7 min-w-[2.75rem] bg-sky-100 px-2 text-xs text-sky-800' => ! $prominent, + ])>{{ $number }} @if ($status) $status === 'waiting', 'bg-indigo-100 text-indigo-800' => $status === 'called', 'bg-emerald-100 text-emerald-800' => $status === 'serving', @@ -21,7 +26,7 @@
@if ($showAssignment)

- @if ($destination){{ $destination }}@endif + @if ($destination){{ $destination }}@endif @if ($destination && $staffDisplayName) · @endif @if ($staffDisplayName){{ $staffDisplayName }}@endif

diff --git a/resources/views/care/prescriptions/queue.blade.php b/resources/views/care/prescriptions/queue.blade.php index e273d85..7a860e1 100644 --- a/resources/views/care/prescriptions/queue.blade.php +++ b/resources/views/care/prescriptions/queue.blade.php @@ -16,12 +16,13 @@ @endif
- @include('care.partials.queue-ops', [ - 'queueIntegration' => $queueIntegration ?? null, - 'queueCallNextRoute' => 'care.prescriptions.call-next', - 'queueCallNextParams' => [], - 'branchId' => $branchId ?? null, - ]) + @include('care.partials.queue-ops', [ + 'queueIntegration' => $queueIntegration ?? null, + 'queueCallNextRoute' => 'care.prescriptions.call-next', + 'queueCallNextParams' => [], + 'branchId' => $branchId ?? null, + 'variant' => 'bar', + ])
diff --git a/resources/views/care/queue/index.blade.php b/resources/views/care/queue/index.blade.php index 84e0c63..61a77b2 100644 --- a/resources/views/care/queue/index.blade.php +++ b/resources/views/care/queue/index.blade.php @@ -6,12 +6,12 @@
Dentistry reports

Procedures, revenue, unfinished plans, and imaging volume.

- ← Back to queue + ← Specialty home
diff --git a/resources/views/care/specialty/emergency/reports.blade.php b/resources/views/care/specialty/emergency/reports.blade.php index 67e2ebf..b595d1d 100644 --- a/resources/views/care/specialty/emergency/reports.blade.php +++ b/resources/views/care/specialty/emergency/reports.blade.php @@ -5,7 +5,7 @@

Emergency reports

Arrivals, acuity mix, dispositions, length of stay, and ED service revenue.

- ← Back to queue + ← Specialty home
diff --git a/resources/views/care/specialty/partials/actions-menu.blade.php b/resources/views/care/specialty/partials/actions-menu.blade.php index 1858601..c567750 100644 --- a/resources/views/care/specialty/partials/actions-menu.blade.php +++ b/resources/views/care/specialty/partials/actions-menu.blade.php @@ -55,6 +55,7 @@ 'queueCallNextParams' => ['module' => $moduleKey], 'branchId' => $branchId ?? null, 'currentAppointmentId' => $appointment?->id, + 'variant' => 'button', ]) @endif @@ -136,9 +137,15 @@ @endif @endif +@if (! empty($canAccessPatientQueue)) Back to queue +@else + + Specialty home + +@endif @if ($canBookAppointments) diff --git a/resources/views/care/specialty/sections/workspace.blade.php b/resources/views/care/specialty/sections/workspace.blade.php index 376ba97..376377a 100644 --- a/resources/views/care/specialty/sections/workspace.blade.php +++ b/resources/views/care/specialty/sections/workspace.blade.php @@ -15,8 +15,16 @@ @if (! $workspaceVisit)

No open visit selected

-

Open a visit from the queue, or call next from the waiting list.

-
Back to queue +

+ @if (! empty($canAccessPatientQueue)) + Open a visit from the queue, or call next from the waiting list. + @else + Open an assigned visit, or ask reception to call the next patient. + @endif +

+ @if (! empty($canAccessPatientQueue)) + Back to queue + @endif
@else {{-- Module tabs --}} diff --git a/resources/views/care/specialty/shell.blade.php b/resources/views/care/specialty/shell.blade.php index 30a2fd3..ee87781 100644 --- a/resources/views/care/specialty/shell.blade.php +++ b/resources/views/care/specialty/shell.blade.php @@ -11,7 +11,11 @@
@if ($section === 'workspace')
- ← Back to queue + @if (! empty($canAccessPatientQueue)) + ← Back to queue + @else + + @endif
@if ($moduleKey === 'dentistry') Reports @@ -78,7 +82,7 @@

Completed encounters for this service line.

- ← Back to queue + ← Specialty home
@include('care.specialty.sections.history') @elseif ($section === 'billing') @@ -90,7 +94,7 @@

Service catalog for this module.

- ← Back to queue + ← Specialty home @include('care.specialty.sections.billing') @else @@ -105,9 +109,9 @@

{{ $specialtyLabel }}

']; - if ($permissions->handlesFloorCare($member)) { + if ($permissions->canAccessPatientQueue($member)) { $nav[] = ['name' => 'Queue', 'route' => route('care.queue.index'), 'active' => request()->routeIs('care.queue.*') || request()->routeIs('care.consultations.*'), 'icon' => '']; } diff --git a/tests/Feature/CareAppointmentTest.php b/tests/Feature/CareAppointmentTest.php index dec975e..33c3fc4 100644 --- a/tests/Feature/CareAppointmentTest.php +++ b/tests/Feature/CareAppointmentTest.php @@ -214,7 +214,7 @@ class CareAppointmentTest extends TestCase $this->actingAs($this->user) ->get(route('care.queue.index', ['branch_id' => $this->branch->id])) ->assertOk() - ->assertSee('Patient queue'); + ->assertSee('Patient flow'); } public function test_queue_repairs_duplicate_positions(): void diff --git a/tests/Feature/CareNaturalQueueTest.php b/tests/Feature/CareNaturalQueueTest.php index 99ec362..4162a60 100644 --- a/tests/Feature/CareNaturalQueueTest.php +++ b/tests/Feature/CareNaturalQueueTest.php @@ -89,7 +89,10 @@ class CareNaturalQueueTest extends TestCase ->get(route('care.queue.index')) ->assertOk() ->assertDontSee('Service counter') - ->assertSee('Call next'); + ->assertSee('Call next') + ->assertSee('Patient flow') + ->assertSee('Called') + ->assertSee('Done'); } public function test_pharmacist_sees_call_next_on_pharmacy_page(): void diff --git a/tests/Feature/CarePatientQueueAccessTest.php b/tests/Feature/CarePatientQueueAccessTest.php new file mode 100644 index 0000000..c20db25 --- /dev/null +++ b/tests/Feature/CarePatientQueueAccessTest.php @@ -0,0 +1,146 @@ +withoutMiddleware(EnsurePlatformSession::class); + + $this->owner = User::create([ + 'public_id' => 'pq-access-owner', + 'name' => 'Owner', + 'email' => 'pq-access-owner@example.com', + ]); + + $this->organization = Organization::create([ + 'owner_ref' => $this->owner->public_id, + 'name' => 'PQ Clinic', + 'slug' => 'pq-clinic', + 'settings' => [ + 'onboarded' => true, + 'facility_type' => 'clinic', + 'plan' => 'pro', + 'plan_expires_at' => now()->addMonth()->toIso8601String(), + 'queue_integration_enabled' => true, + ], + ]); + + Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $this->owner->public_id, + 'role' => 'hospital_admin', + ]); + + $this->branch = Branch::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'name' => 'Main', + 'is_active' => true, + ]); + } + + /** + * @return array{0: User, 1: Member} + */ + protected function makeStaff(string $role, string $suffix): array + { + $user = User::create([ + 'public_id' => 'pq-'.$suffix, + 'name' => ucfirst($role).' '.$suffix, + 'email' => $suffix.'@pq.example.com', + ]); + $member = Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $user->public_id, + 'role' => $role, + 'branch_id' => $this->branch->id, + ]); + + return [$user, $member]; + } + + public function test_nurse_loses_queue_manage_and_cannot_open_patient_queue(): void + { + [$nurseUser, $nurseMember] = $this->makeStaff('nurse', 'nurse1'); + $permissions = app(CarePermissions::class); + + $this->assertFalse($permissions->can($nurseMember, 'queue.manage')); + $this->assertFalse($permissions->canAccessPatientQueue($nurseMember)); + $this->assertTrue($permissions->handlesFloorCare($nurseMember)); + + $this->actingAs($nurseUser) + ->get(route('care.queue.index')) + ->assertForbidden(); + + $this->actingAs($nurseUser) + ->get(route('care.dashboard')) + ->assertOk() + ->assertDontSee('href="'.e(route('care.queue.index')).'"', false); + } + + public function test_doctor_and_receptionist_can_open_redesigned_queue_board(): void + { + [$doctorUser, $doctorMember] = $this->makeStaff('doctor', 'doc1'); + [$receptionistUser, $receptionistMember] = $this->makeStaff('receptionist', 'rec1'); + $permissions = app(CarePermissions::class); + + $this->assertTrue($permissions->canAccessPatientQueue($doctorMember)); + $this->assertTrue($permissions->canAccessPatientQueue($receptionistMember)); + $this->assertTrue($permissions->can($receptionistMember, 'queue.manage')); + + $this->actingAs($doctorUser) + ->get(route('care.queue.index')) + ->assertOk() + ->assertSee('Patient flow') + ->assertSee('Waiting') + ->assertSee('Called') + ->assertSee('Done'); + + $this->actingAs($receptionistUser) + ->get(route('care.queue.index')) + ->assertOk() + ->assertSee('Patient flow') + ->assertSee('Call next'); + + $this->actingAs($doctorUser) + ->get(route('care.dashboard')) + ->assertOk() + ->assertSee('Queue', false); + } + + public function test_nurse_specialty_show_redirects_to_workspace_not_queue(): void + { + [$nurseUser] = $this->makeStaff('nurse', 'nurse-spec'); + + app(\App\Services\Care\SpecialtyModuleService::class) + ->ensureDefaultModulesProvisioned($this->organization, $this->owner->public_id); + app(\App\Services\Care\SpecialtyModuleService::class) + ->activate($this->organization->fresh(), $this->owner->public_id, 'emergency'); + + $this->actingAs($nurseUser) + ->get(route('care.specialty.show', 'emergency')) + ->assertRedirect(route('care.specialty.workspace', 'emergency')); + } +} diff --git a/tests/Feature/CarePractitionerBranchesTest.php b/tests/Feature/CarePractitionerBranchesTest.php index e4543d2..934fa14 100644 --- a/tests/Feature/CarePractitionerBranchesTest.php +++ b/tests/Feature/CarePractitionerBranchesTest.php @@ -121,7 +121,7 @@ class CarePractitionerBranchesTest extends TestCase $this->actingAs($doctor) ->get(route('care.queue.index')) ->assertOk() - ->assertSee('Showing patients assigned to you') + ->assertSee('Assigned to you') ->assertSee('name="branch_id"', false) ->assertSee('East Legon Care') ->assertSee('West Hills Care') diff --git a/tests/Feature/CareSpecialtyModulesTest.php b/tests/Feature/CareSpecialtyModulesTest.php index e65b84d..020182d 100644 --- a/tests/Feature/CareSpecialtyModulesTest.php +++ b/tests/Feature/CareSpecialtyModulesTest.php @@ -231,11 +231,11 @@ class CareSpecialtyModulesTest extends TestCase $this->actingAs($this->owner) ->get(route('care.specialty.show', 'dentistry')) - ->assertRedirect(route('care.queue.index')); + ->assertRedirect(route('care.specialty.workspace', 'dentistry')); $this->actingAs($nurse) ->get(route('care.specialty.show', 'dentistry')) - ->assertRedirect(route('care.queue.index')); + ->assertRedirect(route('care.specialty.workspace', 'dentistry')); } public function test_unassigned_gp_can_open_limited_specialty_module(): void @@ -357,11 +357,11 @@ class CareSpecialtyModulesTest extends TestCase $this->actingAs($this->owner) ->get(route('care.specialty.show', 'emergency')) - ->assertRedirect(route('care.queue.index')); + ->assertRedirect(route('care.specialty.workspace', 'emergency')); $this->actingAs($nurse) ->get(route('care.specialty.show', 'emergency')) - ->assertRedirect(route('care.queue.index')); + ->assertRedirect(route('care.specialty.workspace', 'emergency')); $this->expectException(\RuntimeException::class); $service->deactivate($this->organization->fresh(), $this->owner->public_id, 'emergency'); diff --git a/tests/Feature/CareSpecialtyShellTest.php b/tests/Feature/CareSpecialtyShellTest.php index ef3cde6..c9a0eb2 100644 --- a/tests/Feature/CareSpecialtyShellTest.php +++ b/tests/Feature/CareSpecialtyShellTest.php @@ -96,11 +96,11 @@ class CareSpecialtyShellTest extends TestCase $this->actingAs($this->owner) ->get(route('care.specialty.show', 'emergency')) - ->assertRedirect(route('care.queue.index')); + ->assertRedirect(route('care.specialty.workspace', 'emergency')); $this->actingAs($this->owner) ->get(route('care.specialty.visits', 'emergency')) - ->assertRedirect(route('care.queue.index')); + ->assertRedirect(route('care.specialty.workspace', 'emergency')); $this->actingAs($this->owner) ->get(route('care.specialty.history', 'emergency')) @@ -116,7 +116,8 @@ class CareSpecialtyShellTest extends TestCase $this->actingAs($this->owner) ->get(route('care.specialty.workspace', 'emergency')) ->assertOk() - ->assertSee('Back to queue'); + ->assertSee('No open visit selected') + ->assertDontSee('Back to queue'); } public function test_dentistry_shell_has_chair_stage_and_catalog(): void diff --git a/tests/Feature/CareStaffTenantScopeTest.php b/tests/Feature/CareStaffTenantScopeTest.php index 555c562..5dde303 100644 --- a/tests/Feature/CareStaffTenantScopeTest.php +++ b/tests/Feature/CareStaffTenantScopeTest.php @@ -143,7 +143,7 @@ class CareStaffTenantScopeTest extends TestCase $this->actingAs($doctor) ->get(route('care.queue.index')) ->assertOk() - ->assertSee('Showing patients assigned to you') + ->assertSee('Assigned to you') ->assertSee('East Legon Care') ->assertDontSee('All practitioners') ->assertDontSee('name="branch_id"', false)