Deploy Ladill Queue / deploy (push) Successful in 2m17s
Counters gain destination/staff metadata; tickets can be pre-assigned to a service point with assigned_only queues for healthcare while shared_pool preserves generic bank/government behavior. Display and announcements expose ticket, staff, and destination clearly for Care and public boards. Co-authored-by: Cursor <cursoragent@cursor.com>
128 lines
4.3 KiB
PHP
128 lines
4.3 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.
|
|
$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,
|
|
'announcement_text' => TicketPresenter::announcementText($t, $point),
|
|
];
|
|
})
|
|
->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();
|
|
}
|
|
}
|