Redesign Care patient-flow board and remove Queue from nurses.
Deploy Ladill Care / deploy (push) Successful in 39s
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:
@@ -176,7 +176,8 @@ class ConsultationController extends Controller
|
|||||||
$queueIntegration = ['enabled' => false];
|
$queueIntegration = ['enabled' => false];
|
||||||
$queueBranchId = null;
|
$queueBranchId = null;
|
||||||
$queueBridge = app(CareQueueBridge::class);
|
$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)
|
$queueBranchId = app(BranchContext::class)->resolve($request, $organization, $member)
|
||||||
?: (int) ($consultation->practitioner?->branch_id
|
?: (int) ($consultation->practitioner?->branch_id
|
||||||
?: $consultation->visit?->branch_id
|
?: $consultation->visit?->branch_id
|
||||||
@@ -207,6 +208,7 @@ class ConsultationController extends Controller
|
|||||||
'consultationAssessments' => $consultationAssessments,
|
'consultationAssessments' => $consultationAssessments,
|
||||||
'startableTemplates' => $startableTemplates,
|
'startableTemplates' => $startableTemplates,
|
||||||
'assessmentStatuses' => config('care.assessment_statuses'),
|
'assessmentStatuses' => config('care.assessment_statuses'),
|
||||||
|
'canAccessPatientQueue' => $canAccessPatientQueue,
|
||||||
'universalTemplate' => $universalTemplate,
|
'universalTemplate' => $universalTemplate,
|
||||||
'universalAssessment' => $universalAssessment,
|
'universalAssessment' => $universalAssessment,
|
||||||
'canCaptureUniversal' => $canCaptureUniversal,
|
'canCaptureUniversal' => $canCaptureUniversal,
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ class DashboardController extends Controller
|
|||||||
$canBills = $this->permissions->can($member, 'bills.view');
|
$canBills = $this->permissions->can($member, 'bills.view');
|
||||||
$canFinance = $this->permissions->can($member, 'reports.finance.view');
|
$canFinance = $this->permissions->can($member, 'reports.finance.view');
|
||||||
$canConsult = $this->permissions->can($member, 'consultations.manage');
|
$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);
|
$branchQuery = Branch::owned($owner)->where('organization_id', $organization->id);
|
||||||
$this->scopeToBranch($request, $branchQuery);
|
$this->scopeToBranch($request, $branchQuery);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class QueueController extends Controller
|
|||||||
public function index(Request $request): View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
$this->authorizeAbility($request, 'appointments.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);
|
$organization = $this->organization($request);
|
||||||
$member = $this->member($request);
|
$member = $this->member($request);
|
||||||
$owner = $this->ownerRef($request);
|
$owner = $this->ownerRef($request);
|
||||||
@@ -126,6 +126,24 @@ class QueueController extends Controller
|
|||||||
$todayQuery->where('branch_id', $branchId);
|
$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 = [
|
$heroStats = [
|
||||||
'waiting' => $queue->count(),
|
'waiting' => $queue->count(),
|
||||||
'in_consultation' => $inConsultation->count(),
|
'in_consultation' => $inConsultation->count(),
|
||||||
@@ -149,6 +167,7 @@ class QueueController extends Controller
|
|||||||
'practitionerId' => $practitionerId,
|
'practitionerId' => $practitionerId,
|
||||||
'queue' => $queue,
|
'queue' => $queue,
|
||||||
'inConsultation' => $inConsultation,
|
'inConsultation' => $inConsultation,
|
||||||
|
'done' => $done,
|
||||||
'canManageQueue' => $canManageQueue,
|
'canManageQueue' => $canManageQueue,
|
||||||
'canConsult' => $canConsult,
|
'canConsult' => $canConsult,
|
||||||
'heroStats' => $heroStats,
|
'heroStats' => $heroStats,
|
||||||
@@ -160,7 +179,7 @@ class QueueController extends Controller
|
|||||||
|
|
||||||
public function callNext(Request $request, CareQueueSessionAdvance $advance): RedirectResponse
|
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');
|
$this->authorizeAbility($request, 'appointments.view');
|
||||||
$organization = $this->organization($request);
|
$organization = $this->organization($request);
|
||||||
abort_unless($this->queueBridge->isEnabled($organization), 404);
|
abort_unless($this->queueBridge->isEnabled($organization), 404);
|
||||||
|
|||||||
@@ -39,8 +39,12 @@ class SpecialtyModuleController extends Controller
|
|||||||
403,
|
403,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Specialty patient flow lives on Queue; clinical depth opens from Queue Start.
|
// Specialty patient flow lives on Queue; nurses stay on the specialty workspace.
|
||||||
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 visits(
|
public function visits(
|
||||||
@@ -57,7 +61,11 @@ class SpecialtyModuleController extends Controller
|
|||||||
403,
|
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(
|
public function history(
|
||||||
@@ -543,7 +551,14 @@ class SpecialtyModuleController extends Controller
|
|||||||
);
|
);
|
||||||
|
|
||||||
return redirect()
|
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).'.');
|
->with('success', 'Referred '.$patient->fullName().' to '.($definition['label'] ?? $module).'.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,6 +665,7 @@ class SpecialtyModuleController extends Controller
|
|||||||
|| $permissions->can($member, 'consultations.manage')
|
|| $permissions->can($member, 'consultations.manage')
|
||||||
);
|
);
|
||||||
$canManageQueue = $permissions->can($member, 'appointments.manage') && $canManageSpecialty;
|
$canManageQueue = $permissions->can($member, 'appointments.manage') && $canManageSpecialty;
|
||||||
|
$canAccessPatientQueue = $permissions->canAccessPatientQueue($member);
|
||||||
$canBookAppointments = $permissions->can($member, 'appointments.manage');
|
$canBookAppointments = $permissions->can($member, 'appointments.manage');
|
||||||
$canViewPatients = $permissions->can($member, 'patients.view');
|
$canViewPatients = $permissions->can($member, 'patients.view');
|
||||||
$canViewAppointments = $permissions->can($member, 'appointments.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') ?? '')
|
? (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;
|
$workspaceVisit = null;
|
||||||
$patientHeader = null;
|
$patientHeader = null;
|
||||||
$timeline = [];
|
$timeline = [];
|
||||||
@@ -843,6 +875,7 @@ class SpecialtyModuleController extends Controller
|
|||||||
'waiting' => $waiting,
|
'waiting' => $waiting,
|
||||||
'queue' => $waiting,
|
'queue' => $waiting,
|
||||||
'inConsultation' => $inConsultation,
|
'inConsultation' => $inConsultation,
|
||||||
|
'done' => $done,
|
||||||
'openVisits' => $openVisits,
|
'openVisits' => $openVisits,
|
||||||
'visitHistory' => $visitHistory,
|
'visitHistory' => $visitHistory,
|
||||||
'kpis' => $kpis,
|
'kpis' => $kpis,
|
||||||
@@ -900,6 +933,7 @@ class SpecialtyModuleController extends Controller
|
|||||||
'canManageClinical' => $canManageClinical,
|
'canManageClinical' => $canManageClinical,
|
||||||
'canManageVitals' => $canManageVitals,
|
'canManageVitals' => $canManageVitals,
|
||||||
'canManageQueue' => $canManageQueue,
|
'canManageQueue' => $canManageQueue,
|
||||||
|
'canAccessPatientQueue' => $canAccessPatientQueue,
|
||||||
'canBookAppointments' => $canBookAppointments,
|
'canBookAppointments' => $canBookAppointments,
|
||||||
'canViewPatients' => $canViewPatients,
|
'canViewPatients' => $canViewPatients,
|
||||||
'canViewAppointments' => $canViewAppointments,
|
'canViewAppointments' => $canViewAppointments,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class CarePermissions
|
|||||||
],
|
],
|
||||||
'nurse' => [
|
'nurse' => [
|
||||||
'dashboard.view', 'patients.view', 'appointments.view',
|
'dashboard.view', 'patients.view', 'appointments.view',
|
||||||
'consultations.view', 'vitals.manage', 'queue.manage',
|
'consultations.view', 'vitals.manage',
|
||||||
'service_queues.console',
|
'service_queues.console',
|
||||||
'assessments.view', 'assessments.capture',
|
'assessments.view', 'assessments.capture',
|
||||||
],
|
],
|
||||||
@@ -109,4 +109,17 @@ class CarePermissions
|
|||||||
'cashier',
|
'cashier',
|
||||||
], true);
|
], 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';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,13 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@include('care.partials.queue-ops', [
|
@include('care.partials.queue-ops', [
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
'queueCallNextRoute' => 'care.bills.call-next',
|
'queueCallNextRoute' => 'care.bills.call-next',
|
||||||
'queueCallNextParams' => [],
|
'queueCallNextParams' => [],
|
||||||
'branchId' => $branchId ?? null,
|
'branchId' => $branchId ?? null,
|
||||||
])
|
'variant' => 'bar',
|
||||||
|
])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white">
|
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
'queueCallNextParams' => [],
|
'queueCallNextParams' => [],
|
||||||
'branchId' => $queueBranchId ?? null,
|
'branchId' => $queueBranchId ?? null,
|
||||||
'currentAppointmentId' => $appointment?->id,
|
'currentAppointmentId' => $appointment?->id,
|
||||||
|
'variant' => 'button',
|
||||||
])
|
])
|
||||||
|
|
||||||
@if ($ticketCalled && $appointment)
|
@if ($ticketCalled && $appointment)
|
||||||
@@ -71,4 +72,6 @@
|
|||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if (! empty($canAccessPatientQueue))
|
||||||
<a href="{{ route('care.queue.index') }}" class="{{ $btnSecondary }}">Back to queue</a>
|
<a href="{{ route('care.queue.index') }}" class="{{ $btnSecondary }}">Back to queue</a>
|
||||||
|
@endif
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
'queueCallNextRoute' => 'care.bills.call-next',
|
'queueCallNextRoute' => 'care.bills.call-next',
|
||||||
'queueCallNextParams' => [],
|
'queueCallNextParams' => [],
|
||||||
'branchId' => $branchId ?? null,
|
'branchId' => $branchId ?? null,
|
||||||
|
'variant' => 'button',
|
||||||
])
|
])
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -35,12 +35,13 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@include('care.partials.queue-ops', [
|
@include('care.partials.queue-ops', [
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
'queueCallNextRoute' => 'care.lab.queue.call-next',
|
'queueCallNextRoute' => 'care.lab.queue.call-next',
|
||||||
'queueCallNextParams' => [],
|
'queueCallNextParams' => [],
|
||||||
'branchId' => $branchId,
|
'branchId' => $branchId,
|
||||||
])
|
'variant' => 'bar',
|
||||||
|
])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 space-y-3">
|
<div class="mt-4 space-y-3">
|
||||||
|
|||||||
@@ -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
|
||||||
|
<article @class([
|
||||||
|
'group relative flex gap-3 rounded-xl border px-3 py-3 transition',
|
||||||
|
'border-indigo-200 bg-indigo-50/70 ring-1 ring-indigo-100' => $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',
|
||||||
|
])>
|
||||||
|
<div class="min-w-0 flex-1 space-y-1.5">
|
||||||
|
@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')
|
||||||
|
<span class="inline-flex rounded-md bg-amber-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-800">Unassigned</span>
|
||||||
|
@elseif ($appointment->queue_position)
|
||||||
|
<span class="inline-flex h-8 min-w-[2rem] items-center justify-center rounded-lg bg-slate-200/80 px-2 font-mono text-sm font-bold text-slate-700">{{ $appointment->queue_position }}</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p class="truncate text-sm font-semibold text-slate-900">{{ $appointment->patient?->fullName() ?? 'Patient' }}</p>
|
||||||
|
<p class="mt-0.5 truncate text-xs text-slate-500">
|
||||||
|
@if ($stage === 'in_care' || $stage === 'done')
|
||||||
|
{{ $appointment->practitioner?->name ?? 'Unassigned' }}
|
||||||
|
@if ($appointment->patient?->patient_number)
|
||||||
|
<span class="text-slate-300"> · </span>{{ $appointment->patient->patient_number }}
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
{{ $appointment->patient?->patient_number ?? '—' }}
|
||||||
|
@if ($appointment->reason)
|
||||||
|
<span class="text-slate-300"> · </span>{{ $appointment->reason }}
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex shrink-0 flex-col items-end justify-center gap-1.5">
|
||||||
|
@if ($stage === 'waiting' || $stage === 'called')
|
||||||
|
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && $isCalled && $canConsult)
|
||||||
|
<form method="POST" action="{{ route('care.queue.recall', $appointment) }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="rounded-lg border border-indigo-200 bg-white px-2.5 py-1 text-[11px] font-medium text-indigo-700 hover:bg-indigo-50">Call again</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@if ($canConsult)
|
||||||
|
<form method="POST" action="{{ route($startRouteName, $appointment) }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" @class([
|
||||||
|
'rounded-lg px-2.5 py-1.5 text-xs font-semibold text-white',
|
||||||
|
'bg-indigo-600 hover:bg-indigo-700' => $isCalled,
|
||||||
|
'bg-slate-800 hover:bg-slate-900' => ! $isCalled,
|
||||||
|
])>
|
||||||
|
{{ ! empty($queueIntegration['enabled']) && $ticketStatus === 'called' ? 'Serve & start' : 'Start' }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@elseif ($stage === 'in_care')
|
||||||
|
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && $isCalled && $canConsult)
|
||||||
|
<form method="POST" action="{{ route('care.queue.recall', $appointment) }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="rounded-lg border border-indigo-200 bg-white px-2.5 py-1 text-[11px] font-medium text-indigo-700 hover:bg-indigo-50">Call again</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@if ($openUrl)
|
||||||
|
<a href="{{ $openUrl }}" class="rounded-lg bg-emerald-600 px-2.5 py-1.5 text-xs font-semibold text-white hover:bg-emerald-700">Open</a>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
@@ -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:
|
Expected vars:
|
||||||
- $queue, $inConsultation (collections of Appointment)
|
- $queue, $inConsultation (collections of Appointment)
|
||||||
|
- $done (optional — completed today)
|
||||||
- $queueIntegration, $branchId
|
- $queueIntegration, $branchId
|
||||||
- $canConsult (bool)
|
- $canConsult (bool)
|
||||||
- $queueCallNextRoute, $queueCallNextParams (optional — if set, Call next renders above board)
|
- $queueCallNextRoute, $queueCallNextParams (optional — if set, Call next renders above board)
|
||||||
- $startRouteName (default care.queue.start) — route(name, $appointment)
|
- $startRouteName (default care.queue.start) — route(name, $appointment)
|
||||||
- $openInCareUrl (callable Appointment $a): ?string — link for in-care Open
|
- $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)
|
- $lockToPractitioner, $canSwitchBranch, $branches, $practitioners, $practitionerId (filter bar)
|
||||||
- $showFilters (default true)
|
- $showFilters (default true)
|
||||||
--}}
|
--}}
|
||||||
@php
|
@php
|
||||||
$canConsult = $canConsult ?? false;
|
$canConsult = $canConsult ?? false;
|
||||||
$showFilters = $showFilters ?? true;
|
$showFilters = $showFilters ?? true;
|
||||||
$inCareLabel = $inCareLabel ?? 'In consultation';
|
$inCareLabel = $inCareLabel ?? 'In care';
|
||||||
$startRouteName = $startRouteName ?? 'care.queue.start';
|
$startRouteName = $startRouteName ?? 'care.queue.start';
|
||||||
$openInCareUrl = $openInCareUrl ?? function ($appointment) {
|
$openInCareUrl = $openInCareUrl ?? function ($appointment) {
|
||||||
return $appointment->consultation
|
return $appointment->consultation
|
||||||
@@ -24,10 +25,58 @@
|
|||||||
};
|
};
|
||||||
$queueCallNextRoute = $queueCallNextRoute ?? null;
|
$queueCallNextRoute = $queueCallNextRoute ?? null;
|
||||||
$queueCallNextParams = $queueCallNextParams ?? [];
|
$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
|
@endphp
|
||||||
|
|
||||||
@if ($showFilters)
|
@if ($showFilters)
|
||||||
<form method="GET" class="flex flex-wrap gap-3 rounded-2xl border border-slate-200 bg-white p-4">
|
<form method="GET" class="flex flex-wrap items-center gap-3 rounded-xl border border-slate-200 bg-white px-4 py-3">
|
||||||
@if ($lockToPractitioner ?? false)
|
@if ($lockToPractitioner ?? false)
|
||||||
@if (($canSwitchBranch ?? false) && isset($branches) && $branches->count() > 1)
|
@if (($canSwitchBranch ?? false) && isset($branches) && $branches->count() > 1)
|
||||||
<select name="branch_id" class="rounded-lg border-slate-300 text-sm" onchange="this.form.submit()">
|
<select name="branch_id" class="rounded-lg border-slate-300 text-sm" onchange="this.form.submit()">
|
||||||
@@ -36,11 +85,11 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
@elseif (isset($branches) && $branches->isNotEmpty())
|
@elseif (isset($branches) && $branches->isNotEmpty())
|
||||||
<span class="flex items-center text-sm font-medium text-slate-700">{{ $branches->first()->name }}</span>
|
<span class="text-sm font-medium text-slate-700">{{ $branches->first()->name }}</span>
|
||||||
@elseif (! empty($branchLabel))
|
@elseif (! empty($branchLabel))
|
||||||
<span class="flex items-center text-sm font-medium text-slate-700">{{ $branchLabel }}</span>
|
<span class="text-sm font-medium text-slate-700">{{ $branchLabel }}</span>
|
||||||
@endif
|
@endif
|
||||||
<span class="flex items-center text-sm text-slate-500">Showing patients assigned to you</span>
|
<span class="text-sm text-slate-500">Assigned to you</span>
|
||||||
@elseif ($canSwitchBranch ?? false)
|
@elseif ($canSwitchBranch ?? false)
|
||||||
<select name="branch_id" class="rounded-lg border-slate-300 text-sm" onchange="this.form.submit()">
|
<select name="branch_id" class="rounded-lg border-slate-300 text-sm" onchange="this.form.submit()">
|
||||||
@foreach ($branches as $branch)
|
@foreach ($branches as $branch)
|
||||||
@@ -57,9 +106,9 @@
|
|||||||
@else
|
@else
|
||||||
@if (isset($branches) && $branches->isNotEmpty())
|
@if (isset($branches) && $branches->isNotEmpty())
|
||||||
<input type="hidden" name="branch_id" value="{{ $branchId }}">
|
<input type="hidden" name="branch_id" value="{{ $branchId }}">
|
||||||
<span class="flex items-center text-sm font-medium text-slate-700">{{ $branches->first()->name }}</span>
|
<span class="text-sm font-medium text-slate-700">{{ $branches->first()->name }}</span>
|
||||||
@elseif (! empty($branchLabel))
|
@elseif (! empty($branchLabel))
|
||||||
<span class="flex items-center text-sm font-medium text-slate-700">{{ $branchLabel }}</span>
|
<span class="text-sm font-medium text-slate-700">{{ $branchLabel }}</span>
|
||||||
@endif
|
@endif
|
||||||
@if (isset($practitioners))
|
@if (isset($practitioners))
|
||||||
<select name="practitioner_id" class="rounded-lg border-slate-300 text-sm">
|
<select name="practitioner_id" class="rounded-lg border-slate-300 text-sm">
|
||||||
@@ -74,103 +123,50 @@
|
|||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($queueCallNextRoute)
|
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-100/80">
|
||||||
@include('care.partials.queue-ops', [
|
@if ($queueCallNextRoute)
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
<div class="border-b border-slate-100 bg-gradient-to-r from-indigo-50/90 via-white to-slate-50 px-4 py-3 sm:px-5">
|
||||||
'queueCallNextRoute' => $queueCallNextRoute,
|
@include('care.partials.queue-ops', [
|
||||||
'queueCallNextParams' => $queueCallNextParams,
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
'branchId' => $branchId ?? null,
|
'queueCallNextRoute' => $queueCallNextRoute,
|
||||||
])
|
'queueCallNextParams' => $queueCallNextParams,
|
||||||
@endif
|
'branchId' => $branchId ?? null,
|
||||||
|
'variant' => 'bar',
|
||||||
<div class="grid gap-6 lg:grid-cols-2">
|
])
|
||||||
<section class="rounded-2xl border border-slate-200 bg-white p-6">
|
@if (empty($queueIntegration['enabled']))
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Waiting ({{ $queue->count() }})</h2>
|
<p class="text-sm text-slate-500">Start patients from the waiting column when they arrive at your desk.</p>
|
||||||
<div class="mt-4 space-y-3">
|
@endif
|
||||||
@forelse ($queue as $appointment)
|
|
||||||
<div @class([
|
|
||||||
'flex items-start justify-between gap-4 rounded-xl border p-4',
|
|
||||||
'border-indigo-200 bg-indigo-50/60' => ($appointment->queue_ticket_status ?? null) === 'called',
|
|
||||||
'border-emerald-200 bg-emerald-50' => ($appointment->queue_ticket_status ?? null) === 'serving',
|
|
||||||
'border-slate-100 bg-slate-50' => ! in_array($appointment->queue_ticket_status ?? null, ['called', 'serving'], true),
|
|
||||||
])>
|
|
||||||
<div class="min-w-0 flex-1 space-y-2">
|
|
||||||
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_number)
|
|
||||||
@include('care.partials.queue-ticket', [
|
|
||||||
'ticketNumber' => $appointment->queue_ticket_number,
|
|
||||||
'ticketStatus' => $appointment->queue_ticket_status,
|
|
||||||
'destination' => $appointment->queue_destination,
|
|
||||||
'staffDisplayName' => $appointment->queue_staff_display_name,
|
|
||||||
])
|
|
||||||
@elseif (! empty($queueIntegration['enabled']) && ($appointment->queue_routing_status ?? '') === 'unresolved')
|
|
||||||
<span class="inline-flex rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-800">Unassigned</span>
|
|
||||||
@elseif ($appointment->queue_position)
|
|
||||||
<span class="inline-flex h-7 w-7 items-center justify-center rounded-full bg-sky-100 text-xs font-bold text-sky-700">{{ $appointment->queue_position }}</span>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="truncate text-base font-semibold text-slate-900">{{ $appointment->patient?->fullName() ?? 'Patient' }}</p>
|
|
||||||
<p class="mt-0.5 truncate text-xs text-slate-500">{{ $appointment->patient?->patient_number ?? '—' }} · {{ $appointment->reason ?? '—' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
|
||||||
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && in_array($appointment->queue_ticket_status ?? '', ['called', 'serving'], true) && $canConsult)
|
|
||||||
<form method="POST" action="{{ route('care.queue.recall', $appointment) }}">
|
|
||||||
@csrf
|
|
||||||
<button type="submit" class="rounded-lg border border-indigo-200 bg-indigo-50 px-3 py-1.5 text-xs font-medium text-indigo-700 hover:bg-indigo-100">Call again</button>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
@if ($canConsult)
|
|
||||||
<form method="POST" action="{{ route($startRouteName, $appointment) }}">
|
|
||||||
@csrf
|
|
||||||
<button type="submit" class="rounded-lg bg-sky-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-sky-700">
|
|
||||||
{{ ! empty($queueIntegration['enabled']) && ($appointment->queue_ticket_status ?? '') === 'called' ? 'Serve & start' : 'Start' }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@empty
|
|
||||||
<p class="text-sm text-slate-500">No patients waiting.</p>
|
|
||||||
@endforelse
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
@endif
|
||||||
|
|
||||||
<section class="rounded-2xl border border-slate-200 bg-white p-6">
|
<div class="flex snap-x snap-mandatory gap-0 overflow-x-auto lg:grid lg:grid-cols-4 lg:overflow-visible" style="-ms-overflow-style:none;scrollbar-width:none;">
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">{{ $inCareLabel }} ({{ $inConsultation->count() }})</h2>
|
@foreach ($stages as $index => $stage)
|
||||||
<div class="mt-4 space-y-3">
|
<section @class([
|
||||||
@forelse ($inConsultation as $appointment)
|
'flex w-[min(85vw,20rem)] shrink-0 snap-start flex-col border-slate-100 lg:w-auto',
|
||||||
<div class="flex items-start justify-between gap-4 rounded-xl border border-emerald-100 bg-emerald-50 p-4">
|
'border-r' => $index < count($stages) - 1,
|
||||||
<div class="min-w-0 flex-1 space-y-2">
|
])>
|
||||||
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_number)
|
<header class="sticky top-0 z-[1] flex items-center justify-between gap-2 border-b border-slate-100 bg-white/95 px-4 py-3 backdrop-blur-sm">
|
||||||
@include('care.partials.queue-ticket', [
|
<div class="flex items-center gap-2">
|
||||||
'ticketNumber' => $appointment->queue_ticket_number,
|
<span class="h-2 w-2 rounded-full {{ $stage['dot'] }}"></span>
|
||||||
'ticketStatus' => $appointment->queue_ticket_status,
|
<h2 class="text-xs font-semibold uppercase tracking-wide {{ $stage['accent'] }}">{{ $stage['label'] }}</h2>
|
||||||
'showAssignment' => false,
|
|
||||||
])
|
|
||||||
@endif
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="truncate text-base font-semibold text-slate-900">{{ $appointment->patient?->fullName() ?? 'Patient' }}</p>
|
|
||||||
<p class="mt-0.5 truncate text-xs text-slate-500">{{ $appointment->practitioner?->name ?? 'Unassigned' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
|
||||||
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && in_array($appointment->queue_ticket_status ?? '', ['called', 'serving'], true) && $canConsult)
|
|
||||||
<form method="POST" action="{{ route('care.queue.recall', $appointment) }}">
|
|
||||||
@csrf
|
|
||||||
<button type="submit" class="rounded-lg border border-indigo-200 bg-indigo-50 px-3 py-1.5 text-xs font-medium text-indigo-700 hover:bg-indigo-100">Call again</button>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
@php $openUrl = $openInCareUrl($appointment); @endphp
|
|
||||||
@if ($openUrl)
|
|
||||||
<a href="{{ $openUrl }}" class="text-sm font-medium text-sky-600 hover:text-sky-700">Open</a>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
<span class="rounded-md bg-slate-100 px-2 py-0.5 text-xs font-semibold tabular-nums text-slate-700">{{ $stage['count'] }}</span>
|
||||||
|
</header>
|
||||||
|
<div class="flex-1 space-y-2 p-3 sm:min-h-[12rem]">
|
||||||
|
@forelse ($stage['items'] as $appointment)
|
||||||
|
@include('care.partials.queue-board-card', [
|
||||||
|
'appointment' => $appointment,
|
||||||
|
'stage' => $stage['key'],
|
||||||
|
'canConsult' => $canConsult,
|
||||||
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
|
'startRouteName' => $startRouteName,
|
||||||
|
'openInCareUrl' => $openInCareUrl,
|
||||||
|
])
|
||||||
|
@empty
|
||||||
|
<p class="rounded-xl border border-dashed border-slate-200 px-3 py-8 text-center text-xs text-slate-400">{{ $stage['empty'] }}</p>
|
||||||
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
@empty
|
</section>
|
||||||
<p class="text-sm text-slate-500">No active encounters.</p>
|
@endforeach
|
||||||
@endforelse
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
$callNextRoute = $queueCallNextRoute ?? null;
|
$callNextRoute = $queueCallNextRoute ?? null;
|
||||||
$callNextParams = $queueCallNextParams ?? [];
|
$callNextParams = $queueCallNextParams ?? [];
|
||||||
$currentAppointmentId = $currentAppointmentId ?? null;
|
$currentAppointmentId = $currentAppointmentId ?? null;
|
||||||
|
$variant = $variant ?? 'bar'; // bar | button
|
||||||
@endphp
|
@endphp
|
||||||
@if (! empty($qi['enabled']) && $callNextRoute)
|
@if (! empty($qi['enabled']) && $callNextRoute)
|
||||||
<form method="POST" action="{{ route($callNextRoute, $callNextParams) }}" class="w-full" @click.stop>
|
<form method="POST" action="{{ route($callNextRoute, $callNextParams) }}" class="w-full" @click.stop>
|
||||||
@@ -19,6 +20,20 @@
|
|||||||
@if (! empty($currentAppointmentId))
|
@if (! empty($currentAppointmentId))
|
||||||
<input type="hidden" name="appointment_id" value="{{ $currentAppointmentId }}">
|
<input type="hidden" name="appointment_id" value="{{ $currentAppointmentId }}">
|
||||||
@endif
|
@endif
|
||||||
<button type="submit" class="btn-primary flex w-full items-center justify-center text-sm">Call next</button>
|
@if ($variant === 'bar')
|
||||||
|
<button type="submit" class="group flex w-full items-center justify-between gap-3 rounded-xl bg-indigo-600 px-4 py-3 text-left shadow-sm transition hover:bg-indigo-700">
|
||||||
|
<span class="min-w-0">
|
||||||
|
<span class="block text-sm font-semibold text-white">Call next</span>
|
||||||
|
<span class="mt-0.5 block truncate text-xs text-indigo-100">Bring the next waiting patient to your desk</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-white/15 text-white transition group-hover:bg-white/25">
|
||||||
|
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
@else
|
||||||
|
<button type="submit" class="btn-primary flex w-full items-center justify-center text-sm">Call next</button>
|
||||||
|
@endif
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -4,14 +4,19 @@
|
|||||||
$destination = $destination ?? null;
|
$destination = $destination ?? null;
|
||||||
$staffDisplayName = $staffDisplayName ?? null;
|
$staffDisplayName = $staffDisplayName ?? null;
|
||||||
$showAssignment = ($showAssignment ?? true) && ($destination || $staffDisplayName);
|
$showAssignment = ($showAssignment ?? true) && ($destination || $staffDisplayName);
|
||||||
|
$prominent = $prominent ?? false;
|
||||||
@endphp
|
@endphp
|
||||||
@if ($number)
|
@if ($number)
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<span class="inline-flex h-7 min-w-[2.75rem] items-center justify-center rounded-md bg-sky-100 px-2 font-mono text-xs font-bold tracking-wide text-sky-800">{{ $number }}</span>
|
<span @class([
|
||||||
|
'inline-flex items-center justify-center rounded-lg font-mono font-bold tracking-wide',
|
||||||
|
'h-9 min-w-[3.25rem] bg-indigo-600 px-2.5 text-base text-white shadow-sm' => $prominent,
|
||||||
|
'h-7 min-w-[2.75rem] bg-sky-100 px-2 text-xs text-sky-800' => ! $prominent,
|
||||||
|
])>{{ $number }}</span>
|
||||||
@if ($status)
|
@if ($status)
|
||||||
<span @class([
|
<span @class([
|
||||||
'rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
|
'rounded-md px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
|
||||||
'bg-amber-100 text-amber-800' => $status === 'waiting',
|
'bg-amber-100 text-amber-800' => $status === 'waiting',
|
||||||
'bg-indigo-100 text-indigo-800' => $status === 'called',
|
'bg-indigo-100 text-indigo-800' => $status === 'called',
|
||||||
'bg-emerald-100 text-emerald-800' => $status === 'serving',
|
'bg-emerald-100 text-emerald-800' => $status === 'serving',
|
||||||
@@ -21,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if ($showAssignment)
|
@if ($showAssignment)
|
||||||
<p class="mt-1 truncate text-xs text-slate-500">
|
<p class="mt-1 truncate text-xs text-slate-500">
|
||||||
@if ($destination){{ $destination }}@endif
|
@if ($destination)<span class="font-medium text-slate-600">{{ $destination }}</span>@endif
|
||||||
@if ($destination && $staffDisplayName)<span class="text-slate-300"> · </span>@endif
|
@if ($destination && $staffDisplayName)<span class="text-slate-300"> · </span>@endif
|
||||||
@if ($staffDisplayName){{ $staffDisplayName }}@endif
|
@if ($staffDisplayName){{ $staffDisplayName }}@endif
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -16,12 +16,13 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@include('care.partials.queue-ops', [
|
@include('care.partials.queue-ops', [
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
'queueCallNextRoute' => 'care.prescriptions.call-next',
|
'queueCallNextRoute' => 'care.prescriptions.call-next',
|
||||||
'queueCallNextParams' => [],
|
'queueCallNextParams' => [],
|
||||||
'branchId' => $branchId ?? null,
|
'branchId' => $branchId ?? null,
|
||||||
])
|
'variant' => 'bar',
|
||||||
|
])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 space-y-3">
|
<div class="mt-4 space-y-3">
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<x-care.page-hero
|
<x-care.page-hero
|
||||||
:badge="$specialtyLabel
|
:badge="$specialtyLabel
|
||||||
? $specialtyLabel.' · Waiting · Encounters'
|
? $specialtyLabel.' · Patient flow'
|
||||||
: 'Waiting room · Consultations · Walk-ins'"
|
: 'Ladill Care · Patient flow'"
|
||||||
title="Patient queue"
|
:title="$specialtyLabel ? $specialtyLabel.' queue' : 'Patient flow'"
|
||||||
:description="$specialtyLabel
|
:description="$specialtyLabel
|
||||||
? 'See who is waiting for '.$specialtyLabel.', start encounters, and keep patient flow moving.'
|
? 'Move '.$specialtyLabel.' patients from waiting through called, in care, and done.'
|
||||||
: 'See who is waiting, start consultations, and keep patient flow moving across your branch.'"
|
: 'Move patients from waiting through called, in care, and done — one board for your branch.'"
|
||||||
:stats="[
|
:stats="[
|
||||||
['value' => number_format($heroStats['waiting']), 'label' => 'Waiting'],
|
['value' => number_format($heroStats['waiting']), 'label' => 'Waiting'],
|
||||||
['value' => number_format($heroStats['in_consultation']), 'label' => $specialtyLabel ? 'In care' : 'In consultation'],
|
['value' => number_format($heroStats['in_consultation']), 'label' => $specialtyLabel ? 'In care' : 'In consultation'],
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
@include('care.partials.queue-board', [
|
@include('care.partials.queue-board', [
|
||||||
'queue' => $queue,
|
'queue' => $queue,
|
||||||
'inConsultation' => $inConsultation,
|
'inConsultation' => $inConsultation,
|
||||||
|
'done' => $done ?? collect(),
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
'branchId' => $branchId,
|
'branchId' => $branchId,
|
||||||
'canConsult' => $canConsult,
|
'canConsult' => $canConsult,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<h1 class="text-xl font-semibold text-slate-900">Dentistry reports</h1>
|
<h1 class="text-xl font-semibold text-slate-900">Dentistry reports</h1>
|
||||||
<p class="mt-1 text-sm text-slate-500">Procedures, revenue, unfinished plans, and imaging volume.</p>
|
<p class="mt-1 text-sm text-slate-500">Procedures, revenue, unfinished plans, and imaging volume.</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600">← Back to queue</a>
|
<a href="{{ route('care.specialty.workspace', 'dentistry') }}" class="text-sm font-medium text-indigo-600">← Specialty home</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="GET" class="flex flex-wrap items-end gap-3 rounded-2xl border border-slate-200 bg-white p-4">
|
<form method="GET" class="flex flex-wrap items-end gap-3 rounded-2xl border border-slate-200 bg-white p-4">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<h1 class="text-xl font-semibold text-slate-900">Emergency reports</h1>
|
<h1 class="text-xl font-semibold text-slate-900">Emergency reports</h1>
|
||||||
<p class="mt-1 text-sm text-slate-500">Arrivals, acuity mix, dispositions, length of stay, and ED service revenue.</p>
|
<p class="mt-1 text-sm text-slate-500">Arrivals, acuity mix, dispositions, length of stay, and ED service revenue.</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600">← Back to queue</a>
|
<a href="{{ route('care.specialty.workspace', 'emergency') }}" class="text-sm font-medium text-indigo-600">← Specialty home</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="GET" class="flex flex-wrap items-end gap-3 rounded-2xl border border-slate-200 bg-white p-4">
|
<form method="GET" class="flex flex-wrap items-end gap-3 rounded-2xl border border-slate-200 bg-white p-4">
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
'queueCallNextParams' => ['module' => $moduleKey],
|
'queueCallNextParams' => ['module' => $moduleKey],
|
||||||
'branchId' => $branchId ?? null,
|
'branchId' => $branchId ?? null,
|
||||||
'currentAppointmentId' => $appointment?->id,
|
'currentAppointmentId' => $appointment?->id,
|
||||||
|
'variant' => 'button',
|
||||||
])
|
])
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@@ -136,9 +137,15 @@
|
|||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if (! empty($canAccessPatientQueue))
|
||||||
<a href="{{ route('care.queue.index') }}" class="{{ $btnSecondary }}">
|
<a href="{{ route('care.queue.index') }}" class="{{ $btnSecondary }}">
|
||||||
Back to queue
|
Back to queue
|
||||||
</a>
|
</a>
|
||||||
|
@else
|
||||||
|
<a href="{{ route('care.specialty.workspace', $moduleKey) }}" class="{{ $btnSecondary }}">
|
||||||
|
Specialty home
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if ($canBookAppointments)
|
@if ($canBookAppointments)
|
||||||
<a href="{{ route('care.appointments.create') }}" class="{{ $btnAccent }}">
|
<a href="{{ route('care.appointments.create') }}" class="{{ $btnAccent }}">
|
||||||
|
|||||||
@@ -15,8 +15,16 @@
|
|||||||
@if (! $workspaceVisit)
|
@if (! $workspaceVisit)
|
||||||
<section class="rounded-2xl border border-dashed border-slate-200 bg-white px-4 py-12 text-center">
|
<section class="rounded-2xl border border-dashed border-slate-200 bg-white px-4 py-12 text-center">
|
||||||
<h2 class="text-sm font-semibold text-slate-900">No open visit selected</h2>
|
<h2 class="text-sm font-semibold text-slate-900">No open visit selected</h2>
|
||||||
<p class="mt-1 text-sm text-slate-500">Open a visit from the queue, or call next from the waiting list.</p>
|
<p class="mt-1 text-sm text-slate-500">
|
||||||
<a href="{{ route('care.queue.index') }}" class="mt-4 inline-flex text-sm font-medium text-indigo-600">Back to queue</a>
|
@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
|
||||||
|
</p>
|
||||||
|
@if (! empty($canAccessPatientQueue))
|
||||||
|
<a href="{{ route('care.queue.index') }}" class="mt-4 inline-flex text-sm font-medium text-indigo-600">Back to queue</a>
|
||||||
|
@endif
|
||||||
</section>
|
</section>
|
||||||
@else
|
@else
|
||||||
{{-- Module tabs --}}
|
{{-- Module tabs --}}
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@if ($section === 'workspace')
|
@if ($section === 'workspace')
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Back to queue</a>
|
@if (! empty($canAccessPatientQueue))
|
||||||
|
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Back to queue</a>
|
||||||
|
@else
|
||||||
|
<span></span>
|
||||||
|
@endif
|
||||||
<div class="flex flex-wrap gap-3 text-sm">
|
<div class="flex flex-wrap gap-3 text-sm">
|
||||||
@if ($moduleKey === 'dentistry')
|
@if ($moduleKey === 'dentistry')
|
||||||
<a href="{{ route('care.specialty.dentistry.reports') }}" class="text-slate-600 hover:text-slate-900">Reports</a>
|
<a href="{{ route('care.specialty.dentistry.reports') }}" class="text-slate-600 hover:text-slate-900">Reports</a>
|
||||||
@@ -78,7 +82,7 @@
|
|||||||
<p class="mt-1 text-sm text-slate-500">Completed encounters for this service line.</p>
|
<p class="mt-1 text-sm text-slate-500">Completed encounters for this service line.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Back to queue</a>
|
<a href="{{ route('care.specialty.workspace', $moduleKey) }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Specialty home</a>
|
||||||
</div>
|
</div>
|
||||||
@include('care.specialty.sections.history')
|
@include('care.specialty.sections.history')
|
||||||
@elseif ($section === 'billing')
|
@elseif ($section === 'billing')
|
||||||
@@ -90,7 +94,7 @@
|
|||||||
<p class="mt-1 text-sm text-slate-500">Service catalog for this module.</p>
|
<p class="mt-1 text-sm text-slate-500">Service catalog for this module.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Back to queue</a>
|
<a href="{{ route('care.specialty.workspace', $moduleKey) }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Specialty home</a>
|
||||||
</div>
|
</div>
|
||||||
@include('care.specialty.sections.billing')
|
@include('care.specialty.sections.billing')
|
||||||
@else
|
@else
|
||||||
@@ -105,9 +109,9 @@
|
|||||||
<p class="text-sm font-semibold text-slate-700">{{ $specialtyLabel }}</p>
|
<p class="text-sm font-semibold text-slate-700">{{ $specialtyLabel }}</p>
|
||||||
</div>
|
</div>
|
||||||
<x-care.page-hero
|
<x-care.page-hero
|
||||||
badge="Waiting · In care · Call next"
|
badge="Patient flow · Call next"
|
||||||
:title="$specialtyLabel.' queue'"
|
:title="$specialtyLabel.' queue'"
|
||||||
description="See who is waiting, start encounters, and keep patient flow moving."
|
description="Move patients from waiting through called, in care, and done."
|
||||||
:stats="[
|
:stats="[
|
||||||
['value' => number_format($kpis['waiting'] ?? $queue->count()), 'label' => 'Waiting'],
|
['value' => number_format($kpis['waiting'] ?? $queue->count()), 'label' => 'Waiting'],
|
||||||
['value' => number_format($kpis['in_progress'] ?? $inConsultation->count()), 'label' => 'In care'],
|
['value' => number_format($kpis['in_progress'] ?? $inConsultation->count()), 'label' => 'In care'],
|
||||||
@@ -130,6 +134,7 @@
|
|||||||
@include('care.partials.queue-board', [
|
@include('care.partials.queue-board', [
|
||||||
'queue' => $queue,
|
'queue' => $queue,
|
||||||
'inConsultation' => $inConsultation,
|
'inConsultation' => $inConsultation,
|
||||||
|
'done' => $done ?? collect(),
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
'queueIntegration' => $queueIntegration ?? null,
|
||||||
'branchId' => $branchId,
|
'branchId' => $branchId,
|
||||||
'branchLabel' => $branchLabel ?? null,
|
'branchLabel' => $branchLabel ?? null,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
if ($permissions->can($member, 'appointments.view')) {
|
if ($permissions->can($member, 'appointments.view')) {
|
||||||
$nav[] = ['name' => 'Appointments', 'route' => route('care.appointments.index'), 'active' => request()->routeIs('care.appointments.*'),
|
$nav[] = ['name' => 'Appointments', 'route' => route('care.appointments.index'), 'active' => request()->routeIs('care.appointments.*'),
|
||||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />'];
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />'];
|
||||||
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.*'),
|
$nav[] = ['name' => 'Queue', 'route' => route('care.queue.index'), 'active' => request()->routeIs('care.queue.*') || request()->routeIs('care.consultations.*'),
|
||||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.25 4.5h13.5a.75.75 0 0 1 .75.75v15.75a.75.75 0 0 1-.75.75H5.25a.75.75 0 0 1-.75-.75V5.25a.75.75 0 0 1 .75-.75Z" />'];
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.25 4.5h13.5a.75.75 0 0 1 .75.75v15.75a.75.75 0 0 1-.75.75H5.25a.75.75 0 0 1-.75-.75V5.25a.75.75 0 0 1 .75-.75Z" />'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class CareAppointmentTest extends TestCase
|
|||||||
$this->actingAs($this->user)
|
$this->actingAs($this->user)
|
||||||
->get(route('care.queue.index', ['branch_id' => $this->branch->id]))
|
->get(route('care.queue.index', ['branch_id' => $this->branch->id]))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Patient queue');
|
->assertSee('Patient flow');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_queue_repairs_duplicate_positions(): void
|
public function test_queue_repairs_duplicate_positions(): void
|
||||||
|
|||||||
@@ -89,7 +89,10 @@ class CareNaturalQueueTest extends TestCase
|
|||||||
->get(route('care.queue.index'))
|
->get(route('care.queue.index'))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertDontSee('Service counter')
|
->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
|
public function test_pharmacist_sees_call_next_on_pharmacy_page(): void
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Http\Middleware\EnsurePlatformSession;
|
||||||
|
use App\Models\Branch;
|
||||||
|
use App\Models\Member;
|
||||||
|
use App\Models\Organization;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Care\CarePermissions;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class CarePatientQueueAccessTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected User $owner;
|
||||||
|
|
||||||
|
protected Organization $organization;
|
||||||
|
|
||||||
|
protected Branch $branch;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -121,7 +121,7 @@ class CarePractitionerBranchesTest extends TestCase
|
|||||||
$this->actingAs($doctor)
|
$this->actingAs($doctor)
|
||||||
->get(route('care.queue.index'))
|
->get(route('care.queue.index'))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Showing patients assigned to you')
|
->assertSee('Assigned to you')
|
||||||
->assertSee('name="branch_id"', false)
|
->assertSee('name="branch_id"', false)
|
||||||
->assertSee('East Legon Care')
|
->assertSee('East Legon Care')
|
||||||
->assertSee('West Hills Care')
|
->assertSee('West Hills Care')
|
||||||
|
|||||||
@@ -231,11 +231,11 @@ class CareSpecialtyModulesTest extends TestCase
|
|||||||
|
|
||||||
$this->actingAs($this->owner)
|
$this->actingAs($this->owner)
|
||||||
->get(route('care.specialty.show', 'dentistry'))
|
->get(route('care.specialty.show', 'dentistry'))
|
||||||
->assertRedirect(route('care.queue.index'));
|
->assertRedirect(route('care.specialty.workspace', 'dentistry'));
|
||||||
|
|
||||||
$this->actingAs($nurse)
|
$this->actingAs($nurse)
|
||||||
->get(route('care.specialty.show', 'dentistry'))
|
->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
|
public function test_unassigned_gp_can_open_limited_specialty_module(): void
|
||||||
@@ -357,11 +357,11 @@ class CareSpecialtyModulesTest extends TestCase
|
|||||||
|
|
||||||
$this->actingAs($this->owner)
|
$this->actingAs($this->owner)
|
||||||
->get(route('care.specialty.show', 'emergency'))
|
->get(route('care.specialty.show', 'emergency'))
|
||||||
->assertRedirect(route('care.queue.index'));
|
->assertRedirect(route('care.specialty.workspace', 'emergency'));
|
||||||
|
|
||||||
$this->actingAs($nurse)
|
$this->actingAs($nurse)
|
||||||
->get(route('care.specialty.show', 'emergency'))
|
->get(route('care.specialty.show', 'emergency'))
|
||||||
->assertRedirect(route('care.queue.index'));
|
->assertRedirect(route('care.specialty.workspace', 'emergency'));
|
||||||
|
|
||||||
$this->expectException(\RuntimeException::class);
|
$this->expectException(\RuntimeException::class);
|
||||||
$service->deactivate($this->organization->fresh(), $this->owner->public_id, 'emergency');
|
$service->deactivate($this->organization->fresh(), $this->owner->public_id, 'emergency');
|
||||||
|
|||||||
@@ -96,11 +96,11 @@ class CareSpecialtyShellTest extends TestCase
|
|||||||
|
|
||||||
$this->actingAs($this->owner)
|
$this->actingAs($this->owner)
|
||||||
->get(route('care.specialty.show', 'emergency'))
|
->get(route('care.specialty.show', 'emergency'))
|
||||||
->assertRedirect(route('care.queue.index'));
|
->assertRedirect(route('care.specialty.workspace', 'emergency'));
|
||||||
|
|
||||||
$this->actingAs($this->owner)
|
$this->actingAs($this->owner)
|
||||||
->get(route('care.specialty.visits', 'emergency'))
|
->get(route('care.specialty.visits', 'emergency'))
|
||||||
->assertRedirect(route('care.queue.index'));
|
->assertRedirect(route('care.specialty.workspace', 'emergency'));
|
||||||
|
|
||||||
$this->actingAs($this->owner)
|
$this->actingAs($this->owner)
|
||||||
->get(route('care.specialty.history', 'emergency'))
|
->get(route('care.specialty.history', 'emergency'))
|
||||||
@@ -116,7 +116,8 @@ class CareSpecialtyShellTest extends TestCase
|
|||||||
$this->actingAs($this->owner)
|
$this->actingAs($this->owner)
|
||||||
->get(route('care.specialty.workspace', 'emergency'))
|
->get(route('care.specialty.workspace', 'emergency'))
|
||||||
->assertOk()
|
->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
|
public function test_dentistry_shell_has_chair_stage_and_catalog(): void
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ class CareStaffTenantScopeTest extends TestCase
|
|||||||
$this->actingAs($doctor)
|
$this->actingAs($doctor)
|
||||||
->get(route('care.queue.index'))
|
->get(route('care.queue.index'))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Showing patients assigned to you')
|
->assertSee('Assigned to you')
|
||||||
->assertSee('East Legon Care')
|
->assertSee('East Legon Care')
|
||||||
->assertDontSee('All practitioners')
|
->assertDontSee('All practitioners')
|
||||||
->assertDontSee('name="branch_id"', false)
|
->assertDontSee('name="branch_id"', false)
|
||||||
|
|||||||
Reference in New Issue
Block a user