diff --git a/app/Http/Controllers/Care/QueueController.php b/app/Http/Controllers/Care/QueueController.php index c35c1c3..5644be5 100644 --- a/app/Http/Controllers/Care/QueueController.php +++ b/app/Http/Controllers/Care/QueueController.php @@ -163,16 +163,29 @@ class QueueController extends Controller abort_unless($branchId > 0, 422); + $context = CareQueueContexts::CONSULTATION; + if ($practitionerId) { + $prac = Practitioner::owned($this->ownerRef($request))->find($practitionerId); + $specialtyContext = $this->queueBridge->contextForPractitioner($organization, $prac); + if ($specialtyContext !== CareQueueContexts::CONSULTATION) { + $context = $specialtyContext; + } + } + $result = $this->queueBridge->callNext( $organization, - CareQueueContexts::CONSULTATION, + $context, $branchId, $member, $practitionerId, ); $ticket = $result['ticket'] ?? null; if (! $ticket) { - return back()->with('info', 'No patients waiting at your consultation service point.'); + $label = $context === CareQueueContexts::CONSULTATION + ? 'consultation' + : $context; + + return back()->with('info', "No patients waiting at your {$label} service point."); } return back()->with('success', 'Called '.$ticket['ticket_number'].' — '.($ticket['customer_name'] ?? 'patient').'.'); @@ -234,6 +247,26 @@ class QueueController extends Controller $this->ownerRef($request), ); + $organization = $this->organization($request); + $appointment = $appointment->fresh(['visit', 'department']); + $specialtyContext = $this->queueBridge->contextForAppointment($organization, $appointment); + + if ($specialtyContext !== CareQueueContexts::CONSULTATION && $appointment->visit) { + $clinical = app(\App\Services\Care\SpecialtyClinicalRecordService::class); + $shell = app(\App\Services\Care\SpecialtyShellService::class); + $preferredTab = collect(array_keys($shell->workspaceTabs($specialtyContext))) + ->first(fn (string $tab) => $clinical->recordTypeForTab($specialtyContext, $tab) !== null) + ?? 'clinical_notes'; + + return redirect() + ->route('care.specialty.workspace', [ + 'module' => $specialtyContext, + 'visit' => $appointment->visit, + 'tab' => $preferredTab, + ]) + ->with('success', 'Encounter started.'); + } + return redirect()->route('care.consultations.show', $consultation) ->with('success', 'Consultation started.'); } diff --git a/app/Http/Controllers/Care/SpecialtyModuleController.php b/app/Http/Controllers/Care/SpecialtyModuleController.php index 5155463..bccead3 100644 --- a/app/Http/Controllers/Care/SpecialtyModuleController.php +++ b/app/Http/Controllers/Care/SpecialtyModuleController.php @@ -137,9 +137,19 @@ class SpecialtyModuleController extends Controller $owner = $this->ownerRef($request); $openConsultation = $appointment->consultation; if ($openConsultation && $openConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED) { + $clinical = app(SpecialtyClinicalRecordService::class); + $shellTabs = app(SpecialtyShellService::class)->workspaceTabs($module); + $preferredTab = collect(array_keys($shellTabs)) + ->first(fn (string $tab) => $clinical->recordTypeForTab($module, $tab) !== null) + ?? 'clinical_notes'; + return redirect() - ->route('care.consultations.show', $openConsultation) - ->with('success', 'Consultation already in progress.'); + ->route('care.specialty.workspace', [ + 'module' => $module, + 'visit' => $visit, + 'tab' => $preferredTab, + ]) + ->with('success', 'Encounter already in progress.'); } try { @@ -180,7 +190,7 @@ class SpecialtyModuleController extends Controller 'visit' => $visit, 'tab' => $preferredTab, ]) - ->with('success', 'Specialty encounter started.'); + ->with('success', 'Encounter started.'); } public function saveClinical( @@ -342,18 +352,18 @@ class SpecialtyModuleController extends Controller if ($waitingAtBranch > 0) { return back()->with( 'info', - "{$waitingAtBranch} patient(s) are waiting in {$definition['label']} at {$branchName}, but none have a callable ticket at a service desk yet. Open Visits and use Start consultation, or check in again so a ticket is issued." + "{$waitingAtBranch} patient(s) are waiting at {$branchName}, but none have a callable ticket at a service desk yet. Use Start on the queue, or check in again so a ticket is issued." ); } if ($waitingOrg > $waitingAtBranch) { return back()->with( 'info', - "No patients waiting in {$definition['label']} at {$branchName}. {$waitingOrg} are waiting at other branches — open Visits or switch branch." + "No patients waiting at {$branchName}. {$waitingOrg} are waiting at other branches — switch branch or open History." ); } - return back()->with('info', 'No patients waiting in this specialty queue.'); + return back()->with('info', 'No patients waiting in this queue.'); } $name = $ticket['customer_name'] ?? 'patient'; @@ -420,7 +430,22 @@ class SpecialtyModuleController extends Controller ->with(['patient', 'practitioner', 'department', 'visit']) ->orderBy('queue_position') ->orderBy('checked_in_at') - ->limit(25) + ->limit(50) + ->get(); + + $inConsultation = Appointment::owned($owner) + ->where('organization_id', $organization->id) + ->where('status', Appointment::STATUS_IN_CONSULTATION) + ->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', 'consultation', 'visit']) + ->orderBy('started_at') + ->limit(50) ->get(); $shellDefinition = $shell->definition($module); @@ -431,6 +456,13 @@ class SpecialtyModuleController extends Controller : collect(); $services = $shell->provisionedServices($organization, $module); + $permissions = app(\App\Services\Care\CarePermissions::class); + $canConsult = $permissions->can($member, 'consultations.manage'); + $canManageQueue = $permissions->can($member, 'appointments.manage'); + $branchLabel = $branchId + ? (string) (\App\Models\Branch::owned($owner)->whereKey($branchId)->value('name') ?? '') + : ''; + $workspaceVisit = null; $patientHeader = null; $timeline = []; @@ -511,6 +543,8 @@ class SpecialtyModuleController extends Controller 'section' => $section, 'navItems' => $shell->navItems($module), 'waiting' => $waiting, + 'queue' => $waiting, + 'inConsultation' => $inConsultation, 'openVisits' => $openVisits, 'visitHistory' => $visitHistory, 'kpis' => $kpis, @@ -528,7 +562,11 @@ class SpecialtyModuleController extends Controller 'visitDocuments' => $visitDocuments, 'queueStubs' => $modules->queueStubsFor($organization, $module), 'branchId' => $branchId, + 'branchLabel' => $branchLabel, 'practitionerScope' => $practitionerScope, + 'lockToPractitioner' => $practitionerScope !== null, + 'canConsult' => $canConsult, + 'canManageQueue' => $canManageQueue, 'queueIntegration' => $branchId ? $queueBridge->listMeta($organization, $module, $branchId) : ['enabled' => false], diff --git a/app/Services/Care/CareQueueBridge.php b/app/Services/Care/CareQueueBridge.php index 53a3921..5d98d63 100644 --- a/app/Services/Care/CareQueueBridge.php +++ b/app/Services/Care/CareQueueBridge.php @@ -40,6 +40,15 @@ class CareQueueBridge return CareQueueContexts::CONSULTATION; } + return $this->contextForDepartment($organization, (int) $departmentId); + } + + public function contextForDepartment(Organization $organization, int $departmentId): string + { + if ($departmentId <= 0) { + return CareQueueContexts::CONSULTATION; + } + foreach ($this->specialties->enabledKeys($organization) as $key) { $deptIds = data_get($organization->settings, "specialty_module_provisioning.{$key}.department_ids", []); if (is_array($deptIds) && in_array($departmentId, array_map('intval', $deptIds), true)) { @@ -50,6 +59,15 @@ class CareQueueBridge return CareQueueContexts::CONSULTATION; } + public function contextForPractitioner(Organization $organization, ?Practitioner $practitioner): string + { + if (! $practitioner?->department_id) { + return CareQueueContexts::CONSULTATION; + } + + return $this->contextForDepartment($organization, (int) $practitioner->department_id); + } + public function issueForAppointment(Organization $organization, Appointment $appointment): ?Appointment { if (! $this->isEnabled($organization) || filled($appointment->queue_ticket_uuid)) { diff --git a/app/Services/Care/CareQueueProvisioner.php b/app/Services/Care/CareQueueProvisioner.php index 2eccb5a..57adf83 100644 --- a/app/Services/Care/CareQueueProvisioner.php +++ b/app/Services/Care/CareQueueProvisioner.php @@ -302,7 +302,7 @@ class CareQueueProvisioner $points = []; $roomIndex = 1; $roomPrefix = CareQueueContexts::isSpecialty($context) - ? (string) (config("care.specialty_modules.{$context}.label") ?? 'Room') + ? 'Bay' : 'Consultation Room'; foreach ($practitioners as $practitioner) { diff --git a/app/Services/Care/DemoTenantSeeder.php b/app/Services/Care/DemoTenantSeeder.php index 446e404..1c29198 100644 --- a/app/Services/Care/DemoTenantSeeder.php +++ b/app/Services/Care/DemoTenantSeeder.php @@ -878,7 +878,7 @@ class DemoTenantSeeder 'member_id' => $assignMember ? $member->id : null, 'name' => $practitionerName, 'specialty' => (string) ($definition['label'] ?? $key), - 'room' => ($definition['label'] ?? 'Specialty').' Bay', + 'room' => 'Bay '.(($branchIndex % 4) + 1), 'is_active' => true, 'deleted_at' => null, ], diff --git a/app/Services/Care/SpecialtyShellService.php b/app/Services/Care/SpecialtyShellService.php index 5270e1e..3c28965 100644 --- a/app/Services/Care/SpecialtyShellService.php +++ b/app/Services/Care/SpecialtyShellService.php @@ -463,12 +463,10 @@ class SpecialtyShellService public function navItems(string $moduleKey): array { + // Queue home is the primary surface; history/billing are secondary links in the shell header. return [ - 'overview' => ['label' => 'Overview', 'route' => 'care.specialty.show'], - 'visits' => ['label' => 'Visits', 'route' => 'care.specialty.visits'], 'history' => ['label' => 'History', 'route' => 'care.specialty.history'], 'billing' => ['label' => 'Billing', 'route' => 'care.specialty.billing'], - 'workspace' => ['label' => 'Workspace', 'route' => 'care.specialty.workspace'], ]; } diff --git a/config/care_specialty_shell.php b/config/care_specialty_shell.php index dfbb4f0..081d5e7 100644 --- a/config/care_specialty_shell.php +++ b/config/care_specialty_shell.php @@ -34,12 +34,12 @@ return [ 'documents' => 'Documents', ], 'actions' => [ - 'call_next' => 'Call next patient', - 'start' => 'Start specialty encounter', + 'call_next' => 'Call next', + 'start' => 'Start', 'complete' => 'Complete', 'order_test' => 'Order test', 'prescribe' => 'Prescribe', - 'invoice' => 'Generate invoice', + 'invoice' => 'Add to invoice', 'discharge' => 'Discharge', ], ], diff --git a/resources/views/care/partials/queue-board.blade.php b/resources/views/care/partials/queue-board.blade.php new file mode 100644 index 0000000..ae9ec61 --- /dev/null +++ b/resources/views/care/partials/queue-board.blade.php @@ -0,0 +1,176 @@ +{{-- + Shared Waiting | In care board (GP Queue + specialty Queue homes). + + Expected vars: + - $queue, $inConsultation (collections of Appointment) + - $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") + - $lockToPractitioner, $canSwitchBranch, $branches, $practitioners, $practitionerId (filter bar) + - $showFilters (default true) +--}} +@php + $canConsult = $canConsult ?? false; + $showFilters = $showFilters ?? true; + $inCareLabel = $inCareLabel ?? 'In consultation'; + $startRouteName = $startRouteName ?? 'care.queue.start'; + $openInCareUrl = $openInCareUrl ?? function ($appointment) { + return $appointment->consultation + ? route('care.consultations.show', $appointment->consultation) + : null; + }; + $queueCallNextRoute = $queueCallNextRoute ?? null; + $queueCallNextParams = $queueCallNextParams ?? []; +@endphp + +@if ($showFilters) +
+@endif + +@if ($queueCallNextRoute) + @include('care.partials.queue-ops', [ + 'queueIntegration' => $queueIntegration ?? null, + 'queueCallNextRoute' => $queueCallNextRoute, + 'queueCallNextParams' => $queueCallNextParams, + 'branchId' => $branchId ?? null, + ]) +@endif + +{{ $appointment->patient?->fullName() ?? 'Patient' }}
+{{ $appointment->patient?->patient_number ?? '—' }} · {{ $appointment->reason ?? '—' }}
+No patients waiting.
+ @endforelse +{{ $appointment->patient?->fullName() ?? 'Patient' }}
+{{ $appointment->practitioner?->name ?? 'Unassigned' }}
+No active encounters.
+ @endforelse +{{ $appointment->patient->fullName() }}
-{{ $appointment->patient->patient_number }} · {{ $appointment->reason ?? '—' }}
-No patients waiting.
- @endforelse -{{ $appointment->patient->fullName() }}
-{{ $appointment->practitioner?->name ?? 'Unassigned' }}
-No active consultations.
- @endforelse -- Dept · {{ $module['department_name'] ?? $module['label'] }} - · Queue · {{ $module['queue_name'] ?? $module['label'] }} + {{ $module['department_name'] ?? $module['label'] }}
@endforeach diff --git a/resources/views/care/specialty/partials/actions-menu.blade.php b/resources/views/care/specialty/partials/actions-menu.blade.php index e71764d..a07809f 100644 --- a/resources/views/care/specialty/partials/actions-menu.blade.php +++ b/resources/views/care/specialty/partials/actions-menu.blade.php @@ -1,17 +1,20 @@ -{{-- Specialty shell action buttons (desktop sticky + mobile sheet). --}} +{{-- Specialty action buttons (desktop sticky + mobile sheet). --}} @php $btn = 'flex w-full items-center justify-center rounded-xl px-3 py-2.5 text-sm font-semibold transition'; $btnPrimary = $btn.' bg-indigo-600 text-white hover:bg-indigo-700'; $btnSecondary = $btn.' border border-slate-200 bg-white text-slate-800 hover:bg-slate-50'; $btnAccent = $btn.' border border-indigo-200 bg-indigo-50 text-indigo-900 hover:bg-indigo-100'; + $onWorkspace = ($section ?? '') === 'workspace'; @endphp -@include('care.partials.queue-ops', [ - 'queueIntegration' => $queueIntegration ?? null, - 'queueCallNextRoute' => 'care.specialty.call-next', - 'queueCallNextParams' => ['module' => $moduleKey], - 'branchId' => $branchId ?? null, -]) +@if ($onWorkspace) + @include('care.partials.queue-ops', [ + 'queueIntegration' => $queueIntegration ?? null, + 'queueCallNextRoute' => 'care.specialty.call-next', + 'queueCallNextParams' => ['module' => $moduleKey], + 'branchId' => $branchId ?? null, + ]) +@endif @if ($workspaceVisit) @php @@ -34,20 +37,16 @@ @if ($consultationInProgress) - Open specialty chart + Open chart @elseif ($canStartConsultation) @endif - - Specialty workspace - @if ($workspaceVisit->patient) Patient chart @@ -58,12 +57,12 @@ Appointment @endif -@else - - Open workspace - @endif + + Back to queue + + Register / book diff --git a/resources/views/care/specialty/sections/workspace.blade.php b/resources/views/care/specialty/sections/workspace.blade.php index 7d6f6b4..854fd29 100644 --- a/resources/views/care/specialty/sections/workspace.blade.php +++ b/resources/views/care/specialty/sections/workspace.blade.php @@ -15,8 +15,8 @@ @if (! $workspaceVisit)Open a visit from the Visits list, or call next from the waiting queue.
- Go to visits +Open a visit from the queue, or call next from the waiting list.
+ Back to queueSpecialty module
-{{ $definition['description'] ?? '' }}
+ @if ($section === 'workspace') +Completed encounters for this service line.
+Service catalog for this module.
+