Deploy Ladill Queue / deploy (push) Successful in 56s
Phases 1–6: tickets, counters, displays, appointments, workflows, rules, analytics, reports, feedback, admin, device API, and Gitea deploy workflow for queue.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
3.8 KiB
PHP
110 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Qms;
|
|
|
|
use App\Models\CustomerFeedback;
|
|
use App\Models\QueueAppointment;
|
|
use App\Models\ServiceQueue;
|
|
use App\Models\Ticket;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class AnalyticsService
|
|
{
|
|
public function __construct(
|
|
protected DashboardStats $dashboard,
|
|
) {}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function overview(string $ownerRef, int $organizationId, ?int $branchId = null): array
|
|
{
|
|
$stats = $this->dashboard->forOrganization($ownerRef, $organizationId, $branchId);
|
|
|
|
$today = Ticket::owned($ownerRef)
|
|
->where('organization_id', $organizationId)
|
|
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
|
->whereDate('issued_at', today());
|
|
|
|
$bySource = (clone $today)
|
|
->selectRaw('source, COUNT(*) as total')
|
|
->groupBy('source')
|
|
->pluck('total', 'source')
|
|
->all();
|
|
|
|
$byPriority = (clone $today)
|
|
->selectRaw('priority, COUNT(*) as total')
|
|
->groupBy('priority')
|
|
->pluck('total', 'priority')
|
|
->all();
|
|
|
|
$appointmentsToday = QueueAppointment::owned($ownerRef)
|
|
->where('organization_id', $organizationId)
|
|
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
|
->whereDate('scheduled_at', today())
|
|
->count();
|
|
|
|
$feedbackAvg = CustomerFeedback::owned($ownerRef)
|
|
->where('organization_id', $organizationId)
|
|
->when($branchId, fn ($q) => $q->whereHas('ticket', fn ($t) => $t->where('branch_id', $branchId)))
|
|
->where('created_at', '>=', now()->subDays(30))
|
|
->avg('rating');
|
|
|
|
$queueLoad = ServiceQueue::owned($ownerRef)
|
|
->where('organization_id', $organizationId)
|
|
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
|
->where('is_active', true)
|
|
->withCount(['tickets as waiting_count' => fn ($q) => $q->where('status', 'waiting')])
|
|
->orderByDesc('waiting_count')
|
|
->limit(5)
|
|
->get()
|
|
->map(fn ($q) => ['name' => $q->name, 'waiting' => $q->waiting_count])
|
|
->all();
|
|
|
|
return array_merge($stats, [
|
|
'tickets_by_source' => $bySource,
|
|
'tickets_by_priority' => $byPriority,
|
|
'appointments_today' => $appointmentsToday,
|
|
'feedback_avg_30d' => round((float) $feedbackAvg, 1),
|
|
'busiest_queues' => $queueLoad,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return list<array{date: string, issued: int, completed: int}>
|
|
*/
|
|
public function dailyTrend(string $ownerRef, int $organizationId, int $days = 14, ?int $branchId = null): array
|
|
{
|
|
$from = now()->subDays($days - 1)->startOfDay();
|
|
|
|
$issued = Ticket::owned($ownerRef)
|
|
->where('organization_id', $organizationId)
|
|
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
|
->where('issued_at', '>=', $from)
|
|
->selectRaw('DATE(issued_at) as d, COUNT(*) as c')
|
|
->groupBy('d')
|
|
->pluck('c', 'd');
|
|
|
|
$completed = Ticket::owned($ownerRef)
|
|
->where('organization_id', $organizationId)
|
|
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
|
->where('status', 'completed')
|
|
->where('completed_at', '>=', $from)
|
|
->selectRaw('DATE(completed_at) as d, COUNT(*) as c')
|
|
->groupBy('d')
|
|
->pluck('c', 'd');
|
|
|
|
$trend = [];
|
|
for ($i = 0; $i < $days; $i++) {
|
|
$date = $from->copy()->addDays($i)->toDateString();
|
|
$trend[] = [
|
|
'date' => $date,
|
|
'issued' => (int) ($issued[$date] ?? 0),
|
|
'completed' => (int) ($completed[$date] ?? 0),
|
|
];
|
|
}
|
|
|
|
return $trend;
|
|
}
|
|
}
|