Fix Call next by advancing Care waiters and reclaiming desk tickets.
Deploy Ladill Care / deploy (push) Successful in 51s

Call next now picks the next board appointment, issues or reclaims its ticket onto the calling desk, and still fires display/voice announcements — so patients are not left stuck when tickets sit on the wrong point after sync-on-GET was removed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 20:18:52 +00:00
co-authored by Cursor
parent eaba400c7c
commit c123097f3c
3 changed files with 397 additions and 17 deletions
+75 -16
View File
@@ -127,7 +127,11 @@ class CareQueueEngine
->where('status', CareQueueTicket::STATUS_WAITING)
->when(
$queue->routing_mode === 'assigned_only',
fn ($q) => $q->where('service_point_id', $point->id),
// Claim desk tickets and unassigned waiters (null point) — never other desks.
fn ($q) => $q->where(function ($inner) use ($point) {
$inner->where('service_point_id', $point->id)
->orWhereNull('service_point_id');
}),
fn ($q) => $q->where(function ($inner) use ($point) {
$inner->whereNull('service_point_id')
->orWhere('service_point_id', $point->id);
@@ -142,28 +146,83 @@ class CareQueueEngine
return null;
}
$waiting->forceFill([
'service_point_id' => $point->id,
'status' => CareQueueTicket::STATUS_CALLED,
'called_at' => now(),
])->save();
return $this->markTicketCalled($waiting, $point, $queue);
});
}
$waiting->setRelation('servicePoint', $point);
$waiting->setRelation('serviceQueue', $queue);
/**
* Call a specific waiting ticket to a service point (Care board ticket handoff).
* Reassigns the ticket when it was waiting on another desk or unassigned.
*
* @return array<string, mixed>|null
*/
public function callWaitingTicket(Organization $organization, string $ticketUuid, string $counterUuid): ?array
{
return DB::transaction(function () use ($organization, $ticketUuid, $counterUuid) {
/** @var CareQueueTicket|null $ticket */
$ticket = CareQueueTicket::query()
->where('organization_id', $organization->id)
->where('uuid', $ticketUuid)
->lockForUpdate()
->first();
try {
app(CareVoiceAnnouncementService::class)->announceCalled($waiting, $point);
} catch (Throwable $e) {
Log::warning('Care voice announcement failed on call-next', [
'ticket_id' => $waiting->id,
'error' => $e->getMessage(),
]);
if (! $ticket || $ticket->status !== CareQueueTicket::STATUS_WAITING) {
return null;
}
return $this->toApiArray($waiting);
/** @var CareServiceQueue|null $queue */
$queue = CareServiceQueue::query()
->where('organization_id', $organization->id)
->whereKey($ticket->service_queue_id)
->lockForUpdate()
->first();
if (! $queue || ! $queue->is_active) {
return null;
}
/** @var CareServicePoint|null $point */
$point = CareServicePoint::query()
->where('organization_id', $organization->id)
->where('service_queue_id', $queue->id)
->where('uuid', $counterUuid)
->where('is_active', true)
->first();
if (! $point) {
return null;
}
return $this->markTicketCalled($ticket, $point, $queue);
});
}
/**
* @return array<string, mixed>
*/
protected function markTicketCalled(CareQueueTicket $ticket, CareServicePoint $point, CareServiceQueue $queue): array
{
$ticket->forceFill([
'service_point_id' => $point->id,
'status' => CareQueueTicket::STATUS_CALLED,
'called_at' => now(),
])->save();
$ticket->setRelation('servicePoint', $point);
$ticket->setRelation('serviceQueue', $queue);
try {
app(CareVoiceAnnouncementService::class)->announceCalled($ticket, $point);
} catch (Throwable $e) {
Log::warning('Care voice announcement failed on call-next', [
'ticket_id' => $ticket->id,
'error' => $e->getMessage(),
]);
}
return $this->toApiArray($ticket);
}
/**
* @param array{action: string} $payload
* @return array<string, mixed>