Restore Care queue FIFO after demo skip left tickets Unassigned.
Deploy Ladill Care / deploy (push) Successful in 1m2s
Deploy Ladill Care / deploy (push) Successful in 1m2s
Provision one branch/context at a time instead of the whole org, fall back to a desk when doctor counters are catching up, sync pharmacy tickets in Care list order on page load, and seed waiting_at/queue_position for demos. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -64,9 +64,16 @@ class PrescriptionController extends Controller
|
||||
$queue = $queue->filter(fn (Prescription $rx) => (int) ($rx->visit?->branch_id) === $branchScope)->values();
|
||||
}
|
||||
|
||||
$queueIntegration = $branchId
|
||||
? $this->queueBridge->listMeta($organization, CareQueueContexts::PHARMACY, $branchId)
|
||||
: ['enabled' => false];
|
||||
$queueIntegration = ['enabled' => false];
|
||||
if ($branchId && $this->queueBridge->isEnabled($organization)) {
|
||||
// Issue tickets in Care FIFO order so Queue Call Next matches this list.
|
||||
$this->queueBridge->syncMissingTickets($organization, CareQueueContexts::PHARMACY, $branchId, 50);
|
||||
$queueIntegration = $this->queueBridge->listMeta($organization, CareQueueContexts::PHARMACY, $branchId);
|
||||
$queue = $this->prescriptions->pharmacyQueue($this->ownerRef($request), $organization->id);
|
||||
if ($branchScope) {
|
||||
$queue = $queue->filter(fn (Prescription $rx) => (int) ($rx->visit?->branch_id) === $branchScope)->values();
|
||||
}
|
||||
}
|
||||
|
||||
$memberPoint = $branchId
|
||||
? app(\App\Services\Care\CareQueueProvisioner::class)
|
||||
|
||||
@@ -47,7 +47,7 @@ class QueueController extends Controller
|
||||
$queueIntegration = ['enabled' => false];
|
||||
if ($branchId && $this->queueBridge->isEnabled($organization)) {
|
||||
// Catch up tickets for waiters created before Queue was linked (demo / pre-enable).
|
||||
$this->queueBridge->syncMissingTickets($organization, CareQueueContexts::CONSULTATION, $branchId, 25);
|
||||
$this->queueBridge->syncMissingTickets($organization, CareQueueContexts::CONSULTATION, $branchId, 50);
|
||||
$queueIntegration = $this->queueBridge->listMeta($organization, CareQueueContexts::CONSULTATION, $branchId);
|
||||
}
|
||||
|
||||
|
||||
@@ -407,26 +407,41 @@ class CareQueueBridge
|
||||
*/
|
||||
protected function resolveConsultationPoint(Organization $organization, Appointment $appointment): ?array
|
||||
{
|
||||
if ($appointment->practitioner_id) {
|
||||
$point = $this->provisioner->pointForPractitioner(
|
||||
$organization,
|
||||
(int) $appointment->branch_id,
|
||||
(int) $appointment->practitioner_id,
|
||||
);
|
||||
if ($point) {
|
||||
return $point;
|
||||
}
|
||||
// Practitioner assigned but point not provisioned yet — refresh once.
|
||||
$this->provisioner->ensure($organization, CareQueueContexts::CONSULTATION, (int) $appointment->branch_id);
|
||||
$branchId = (int) $appointment->branch_id;
|
||||
|
||||
return $this->provisioner->pointForPractitioner(
|
||||
$organization->fresh(),
|
||||
(int) $appointment->branch_id,
|
||||
(int) $appointment->practitioner_id,
|
||||
);
|
||||
// Assigned-only consultation requires a doctor — leave Unassigned until one is chosen.
|
||||
if (! $appointment->practitioner_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
$point = $this->provisioner->pointForPractitioner(
|
||||
$organization,
|
||||
$branchId,
|
||||
(int) $appointment->practitioner_id,
|
||||
);
|
||||
if ($point) {
|
||||
return $point;
|
||||
}
|
||||
|
||||
// Practitioner assigned but point not provisioned yet — refresh this branch only.
|
||||
$this->provisioner->ensure($organization, CareQueueContexts::CONSULTATION, $branchId);
|
||||
|
||||
$point = $this->provisioner->pointForPractitioner(
|
||||
$organization->fresh(),
|
||||
$branchId,
|
||||
(int) $appointment->practitioner_id,
|
||||
);
|
||||
if ($point) {
|
||||
return $point;
|
||||
}
|
||||
|
||||
// Prefer a desk ticket over leaving waiters Unassigned while counters catch up
|
||||
// after demo seed / deferred settings provision.
|
||||
return $this->provisioner->defaultPoint(
|
||||
$organization->fresh(),
|
||||
CareQueueContexts::CONSULTATION,
|
||||
$branchId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,6 +674,7 @@ class CareQueueBridge
|
||||
})
|
||||
->whereHas('visit', fn ($q) => $q->where('branch_id', $branchId))
|
||||
->with(['patient', 'visit'])
|
||||
->orderBy('created_at')
|
||||
->orderBy('id')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
@@ -476,6 +476,9 @@ class CareQueueProvisioner
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Queue resources exist for one context + branch only.
|
||||
* Never fans out across every department × branch (that hung demo queues).
|
||||
*
|
||||
* @return array{queue_uuid: ?string, counter_uuid: ?string, queue_external_key: string, counter_external_key: string, synced: bool, points: list<array<string, mixed>>, routing_mode: string}|null
|
||||
*/
|
||||
public function ensure(
|
||||
@@ -507,8 +510,58 @@ class CareQueueProvisioner
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->provisionOrganization($organization->fresh());
|
||||
$this->provisionContextBranch($organization->fresh(), $context, $branchId);
|
||||
|
||||
return $this->resourcesFor($organization->fresh(), $context, $branchId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provision a single department queue for one branch (idempotent).
|
||||
*/
|
||||
public function provisionContextBranch(Organization $organization, string $context, int $branchId): ?array
|
||||
{
|
||||
if (! $this->shouldProvision($organization)) {
|
||||
return $this->resourcesFor($organization, $context, $branchId);
|
||||
}
|
||||
|
||||
$meta = CareQueueContexts::departments()[$context] ?? null;
|
||||
if ($meta === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$branch = Branch::owned((string) $organization->owner_ref)
|
||||
->where('organization_id', $organization->id)
|
||||
->whereKey($branchId)
|
||||
->first();
|
||||
if (! $branch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$provisioning = (array) data_get($organization->settings, 'department_queue_provisioning', []);
|
||||
$queues = is_array($provisioning[$context]['queues'] ?? null)
|
||||
? $provisioning[$context]['queues']
|
||||
: [];
|
||||
$byBranch = collect($queues)->keyBy(fn ($q) => (string) ($q['branch_id'] ?? ''));
|
||||
$prior = $byBranch->get((string) $branch->id, []);
|
||||
|
||||
$stub = $this->provisionBranchDepartment(
|
||||
$organization,
|
||||
(string) $organization->owner_ref,
|
||||
$context,
|
||||
$meta,
|
||||
$branch,
|
||||
is_array($prior) ? $prior : [],
|
||||
);
|
||||
$byBranch->put((string) $branch->id, $stub);
|
||||
|
||||
$provisioning[$context] = [
|
||||
'queues' => $byBranch->values()->all(),
|
||||
];
|
||||
|
||||
$settings = $organization->settings ?? [];
|
||||
$settings['department_queue_provisioning'] = $provisioning;
|
||||
$organization->update(['settings' => $settings]);
|
||||
|
||||
return $stub;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,6 +896,12 @@ class DemoTenantSeeder
|
||||
'status' => $status,
|
||||
'scheduled_at' => $scheduledAt,
|
||||
'checked_in_at' => $visit?->checked_in_at,
|
||||
'waiting_at' => in_array($status, [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN], true)
|
||||
? $scheduledAt->copy()->addMinutes(5)
|
||||
: null,
|
||||
'queue_position' => in_array($status, [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN], true)
|
||||
? ($i + 1)
|
||||
: null,
|
||||
'completed_at' => $status === Appointment::STATUS_COMPLETED
|
||||
? $scheduledAt->copy()->addHour()
|
||||
: null,
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-sky-100 bg-sky-50/70 px-4 py-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-sky-700">Queue</p>
|
||||
<p class="text-sm text-slate-600">Call next operates on your assigned service point only — tickets routed to other rooms or desks stay there.</p>
|
||||
<p class="text-sm text-slate-600">
|
||||
@if (($qi['routing_mode'] ?? null) === 'shared_pool')
|
||||
Call next takes the next waiting patient in line for this queue (shared pool).
|
||||
@else
|
||||
Call next operates on your assigned service point only — tickets routed to other rooms or desks stay there.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<form method="POST" action="{{ route($callNextRoute, $callNextParams) }}">
|
||||
@csrf
|
||||
|
||||
Reference in New Issue
Block a user