Give specialty modules the same Queue UI as GPs.
Deploy Ladill Care / deploy (push) Successful in 36s
Deploy Ladill Care / deploy (push) Successful in 36s
Specialty homes now use the shared Waiting/In care board with Call next, Start routes specialty tickets into specialty workspaces, and redundant Dentistry/Specialty module chrome is removed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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.');
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user