Files
ladill-queue/app/Services/Qms/DisplayService.php
T
isaaccladandCursor 5356118fd8
Deploy Ladill Queue / deploy (push) Successful in 1m56s
Treat all Now Serving cards equally and announce every ticket change.
Remove newest-card highlighting so every active service point looks the same, sort the board stably by destination, and have the display announce whenever a counter’s ticket changes (falling back when a server voice row is missing).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:20:45 +00:00

140 lines
5.0 KiB
PHP

<?php
namespace App\Services\Qms;
use App\Models\DisplayScreen;
use App\Models\ServiceQueue;
use App\Models\Ticket;
class DisplayService
{
public function findByToken(string $token): ?DisplayScreen
{
return DisplayScreen::query()
->where('access_token', $token)
->where('is_active', true)
->first();
}
public function touch(DisplayScreen $screen): void
{
$screen->update(['last_seen_at' => now()]);
}
/**
* @return array<string, mixed>
*/
public function payload(DisplayScreen $screen): array
{
$screen->loadMissing(['branch', 'organization']);
$queueIds = $this->resolveQueueIds($screen);
$queues = ServiceQueue::query()
->whereIn('id', $queueIds)
->where('is_active', true)
->orderBy('name')
->get();
// One active ticket per destination (service point / counter). Older called/serving
// tickets at the same window must not crowd the public board. Order is stable by
// destination so every serving point is presented equally (no "newest" primacy).
$nowServing = Ticket::query()
->whereIn('service_queue_id', $queueIds)
->whereIn('status', ['called', 'serving'])
->with(['counter', 'assignedCounter', 'serviceQueue'])
->orderByDesc('called_at')
->limit(48)
->get()
->unique(fn (Ticket $t) => $t->counter_id
? 'counter:'.$t->counter_id
: ($t->assigned_counter_id
? 'assigned:'.$t->assigned_counter_id
: 'queue:'.$t->service_queue_id))
->take(12)
->values()
->map(function (Ticket $t) {
$point = $t->counter ?? $t->assignedCounter;
return [
'ticket_number' => $t->ticket_number,
'queue_name' => $t->serviceQueue?->name,
'department' => $t->serviceQueue?->name,
'counter' => $point?->displayDestination() ?? $point?->name,
'destination' => $point?->displayDestination(),
'staff_display_name' => $point?->staff_display_name,
'point_key' => $point
? 'counter:'.$point->id
: 'queue:'.$t->service_queue_id,
'announcement_text' => TicketPresenter::announcementText($t, $point),
'called_at' => optional($t->called_at)?->toIso8601String(),
];
})
->sortBy(fn (array $item) => sprintf(
'%s|%s|%s',
mb_strtolower((string) ($item['queue_name'] ?? '')),
mb_strtolower((string) ($item['destination'] ?? $item['counter'] ?? '')),
(string) ($item['ticket_number'] ?? ''),
))
->values()
->all();
$waiting = Ticket::query()
->whereIn('service_queue_id', $queueIds)
->where('status', 'waiting')
->count();
$avgWait = (int) Ticket::query()
->whereIn('service_queue_id', $queueIds)
->where('status', 'completed')
->whereDate('issued_at', today())
->whereNotNull('called_at')
->get(['issued_at', 'called_at'])
->avg(fn (Ticket $t) => $t->called_at->diffInSeconds($t->issued_at));
return [
'screen' => [
'name' => $screen->name,
'layout' => $screen->layout,
'branch_name' => $screen->branch?->name,
'organization_name' => $screen->organization?->name,
],
'now_serving' => $nowServing,
'waiting_count' => $waiting,
'estimated_wait_minutes' => $avgWait > 0 ? (int) ceil($avgWait / 60) : null,
'queues' => $queues->map(fn (ServiceQueue $q) => [
'name' => $q->name,
'prefix' => $q->prefix,
'is_paused' => (bool) $q->is_paused,
])->values()->all(),
'announcements' => app(VoiceAnnouncementService::class)->pendingForBranch(
(int) $screen->branch_id,
$queueIds,
),
'updated_at' => now()->toIso8601String(),
];
}
/**
* @return list<int>
*/
protected function resolveQueueIds(DisplayScreen $screen): array
{
$ids = array_values(array_unique(array_filter(array_map(
fn ($id) => (int) $id,
$screen->service_queue_ids ?? [],
))));
if ($ids !== []) {
return $ids;
}
return ServiceQueue::query()
->where('organization_id', $screen->organization_id)
->when($screen->branch_id, fn ($query) => $query->where('branch_id', $screen->branch_id))
->where('is_active', true)
->orderBy('name')
->pluck('id')
->map(fn ($id) => (int) $id)
->all();
}
}