Fix queue handoff so tickets keep their number across services.
Deploy Ladill Queue / deploy (push) Successful in 40s
Deploy Ladill Queue / deploy (push) Successful in 40s
Handoff now frees the counter, ends the service session, and requeues the same ticket for the next department — critical for hospital flows like doctor → lab. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -230,35 +230,89 @@ class QueueEngine
|
||||
?string $reason = null,
|
||||
?string $actorRef = null,
|
||||
): Ticket {
|
||||
$fromQueueId = $ticket->service_queue_id;
|
||||
return DB::transaction(function () use ($ticket, $toQueue, $counter, $reason, $actorRef) {
|
||||
$ticket = Ticket::query()->lockForUpdate()->findOrFail($ticket->id);
|
||||
$toQueue = ServiceQueue::query()->lockForUpdate()->findOrFail($toQueue->id);
|
||||
|
||||
$ticket->update([
|
||||
'service_queue_id' => $toQueue->id,
|
||||
'branch_id' => $toQueue->branch_id,
|
||||
'status' => 'waiting',
|
||||
'counter_id' => null,
|
||||
'called_at' => null,
|
||||
'serving_started_at' => null,
|
||||
]);
|
||||
if ($toQueue->id === $ticket->service_queue_id) {
|
||||
throw new \RuntimeException('Ticket is already in that queue.');
|
||||
}
|
||||
|
||||
DB::table('queue_ticket_transfers')->insert([
|
||||
'owner_ref' => $ticket->owner_ref,
|
||||
'ticket_id' => $ticket->id,
|
||||
'from_service_queue_id' => $fromQueueId,
|
||||
'to_service_queue_id' => $toQueue->id,
|
||||
'from_counter_id' => $counter?->id,
|
||||
'reason' => $reason,
|
||||
'transferred_by' => $actorRef,
|
||||
'transferred_at' => now(),
|
||||
]);
|
||||
if ($ticket->organization_id !== $toQueue->organization_id || $ticket->owner_ref !== $toQueue->owner_ref) {
|
||||
throw new \RuntimeException('Cannot hand off to a queue outside this organization.');
|
||||
}
|
||||
|
||||
$this->recordEvent($ticket, 'transferred', $actorRef, $counter?->id, [
|
||||
'to_queue_id' => $toQueue->id,
|
||||
'reason' => $reason,
|
||||
]);
|
||||
$this->audit($ticket, 'ticket.transferred', $actorRef, ['to_queue' => $toQueue->name]);
|
||||
if (! $toQueue->is_active || $toQueue->is_paused) {
|
||||
throw new \RuntimeException('Target queue is not accepting tickets.');
|
||||
}
|
||||
|
||||
return $ticket->fresh(['serviceQueue']);
|
||||
if ($toQueue->max_capacity) {
|
||||
$waiting = Ticket::query()
|
||||
->where('service_queue_id', $toQueue->id)
|
||||
->whereIn('status', ['waiting', 'called', 'serving', 'on_hold'])
|
||||
->count();
|
||||
if ($waiting >= $toQueue->max_capacity) {
|
||||
throw new \RuntimeException('Target queue has reached maximum capacity.');
|
||||
}
|
||||
}
|
||||
|
||||
$fromQueueId = $ticket->service_queue_id;
|
||||
$fromCounterId = $counter?->id ?? $ticket->counter_id;
|
||||
$ticketNumber = $ticket->ticket_number;
|
||||
|
||||
$waitingCount = Ticket::query()
|
||||
->where('service_queue_id', $toQueue->id)
|
||||
->where('status', 'waiting')
|
||||
->count();
|
||||
$estimatedWait = $waitingCount * (int) $toQueue->avg_service_seconds;
|
||||
|
||||
// Same ticket row and number — only the queue assignment changes.
|
||||
$ticket->update([
|
||||
'service_queue_id' => $toQueue->id,
|
||||
'branch_id' => $toQueue->branch_id,
|
||||
'status' => 'waiting',
|
||||
'position' => $waitingCount + 1,
|
||||
'estimated_wait_seconds' => $estimatedWait,
|
||||
'counter_id' => null,
|
||||
'called_at' => null,
|
||||
'serving_started_at' => null,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
|
||||
if ($fromCounterId) {
|
||||
Counter::where('id', $fromCounterId)->update(['status' => 'available']);
|
||||
}
|
||||
|
||||
app(ServiceSessionTracker::class)->endForTicket($ticket);
|
||||
|
||||
DB::table('queue_ticket_transfers')->insert([
|
||||
'owner_ref' => $ticket->owner_ref,
|
||||
'ticket_id' => $ticket->id,
|
||||
'from_service_queue_id' => $fromQueueId,
|
||||
'to_service_queue_id' => $toQueue->id,
|
||||
'from_counter_id' => $fromCounterId,
|
||||
'reason' => $reason,
|
||||
'transferred_by' => $actorRef,
|
||||
'transferred_at' => now(),
|
||||
]);
|
||||
|
||||
$this->recordEvent($ticket, 'transferred', $actorRef, $fromCounterId, [
|
||||
'from_queue_id' => $fromQueueId,
|
||||
'to_queue_id' => $toQueue->id,
|
||||
'ticket_number' => $ticketNumber,
|
||||
'reason' => $reason,
|
||||
]);
|
||||
$this->audit($ticket, 'ticket.transferred', $actorRef, [
|
||||
'to_queue' => $toQueue->name,
|
||||
'ticket_number' => $ticketNumber,
|
||||
]);
|
||||
|
||||
$ticket = $ticket->fresh(['serviceQueue', 'counter']);
|
||||
app(QueueNotificationService::class)->ticketTransferred($ticket);
|
||||
$this->notifyWaitingPositions($toQueue);
|
||||
|
||||
return $ticket;
|
||||
});
|
||||
}
|
||||
|
||||
public function pauseQueue(ServiceQueue $queue, ?string $actorRef = null): ServiceQueue
|
||||
|
||||
Reference in New Issue
Block a user