Add Care waiting-area TV displays with browser TTS voice.
Deploy Ladill Care / deploy (push) Failing after 57s
Deploy Ladill Care / deploy (push) Failing after 57s
Call-next and recall queue announcements for lobby screens without Ladill Queue. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Care;
|
||||
|
||||
use App\Models\CareDisplayScreen;
|
||||
use App\Models\CareQueueTicket;
|
||||
use App\Models\CareServicePoint;
|
||||
use App\Models\CareServiceQueue;
|
||||
|
||||
class CareDisplayService
|
||||
{
|
||||
public function findByToken(string $token): ?CareDisplayScreen
|
||||
{
|
||||
return CareDisplayScreen::query()
|
||||
->where('access_token', $token)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function touch(CareDisplayScreen $screen): void
|
||||
{
|
||||
$screen->update(['last_seen_at' => now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function payload(CareDisplayScreen $screen): array
|
||||
{
|
||||
$screen->loadMissing(['branch', 'organization']);
|
||||
$queueIds = $this->resolveQueueIds($screen);
|
||||
$queues = CareServiceQueue::query()
|
||||
->whereIn('id', $queueIds)
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
// One active ticket per service point. Older called/serving tickets at the
|
||||
// same window must not crowd the public board.
|
||||
$nowServing = CareQueueTicket::query()
|
||||
->whereIn('service_queue_id', $queueIds)
|
||||
->whereIn('status', [
|
||||
CareQueueTicket::STATUS_CALLED,
|
||||
CareQueueTicket::STATUS_SERVING,
|
||||
])
|
||||
->with(['servicePoint', 'serviceQueue'])
|
||||
->orderByDesc('called_at')
|
||||
->limit(48)
|
||||
->get()
|
||||
->unique(fn (CareQueueTicket $t) => $t->service_point_id
|
||||
? 'point:'.$t->service_point_id
|
||||
: 'queue:'.$t->service_queue_id)
|
||||
->take(12)
|
||||
->values()
|
||||
->map(function (CareQueueTicket $t) {
|
||||
$point = $t->servicePoint;
|
||||
|
||||
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
|
||||
? 'point:'.$point->id
|
||||
: 'queue:'.$t->service_queue_id,
|
||||
'announcement_text' => $this->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 = CareQueueTicket::query()
|
||||
->whereIn('service_queue_id', $queueIds)
|
||||
->where('status', CareQueueTicket::STATUS_WAITING)
|
||||
->count();
|
||||
|
||||
$avgWait = (int) CareQueueTicket::query()
|
||||
->whereIn('service_queue_id', $queueIds)
|
||||
->where('status', CareQueueTicket::STATUS_COMPLETED)
|
||||
->whereDate('created_at', today())
|
||||
->whereNotNull('called_at')
|
||||
->get(['created_at', 'called_at'])
|
||||
->avg(fn (CareQueueTicket $t) => $t->called_at->diffInSeconds($t->created_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 (CareServiceQueue $q) => [
|
||||
'name' => $q->name,
|
||||
'prefix' => $q->prefix,
|
||||
'is_paused' => false,
|
||||
])->values()->all(),
|
||||
'announcements' => app(CareVoiceAnnouncementService::class)->pendingForBranch(
|
||||
(int) $screen->branch_id,
|
||||
$queueIds,
|
||||
),
|
||||
'updated_at' => now()->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function announcementText(CareQueueTicket $ticket, ?CareServicePoint $point): ?string
|
||||
{
|
||||
if (! $point) {
|
||||
return $ticket->ticket_number;
|
||||
}
|
||||
|
||||
$parts = array_values(array_filter([
|
||||
$ticket->ticket_number,
|
||||
$point->staff_display_name ?: null,
|
||||
$ticket->serviceQueue?->name,
|
||||
$point->displayDestination(),
|
||||
]));
|
||||
|
||||
return implode(', ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
protected function resolveQueueIds(CareDisplayScreen $screen): array
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map(
|
||||
fn ($id) => (int) $id,
|
||||
$screen->service_queue_ids ?? [],
|
||||
))));
|
||||
|
||||
if ($ids !== []) {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
return CareServiceQueue::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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user