Files
ladill-queue/app/Services/Qms/AnalyticsService.php
T
isaaccladandCursor cca98eefd2
Deploy Ladill Queue / deploy (push) Successful in 56s
Initial Ladill Queue release — enterprise QMS standalone app.
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>
2026-06-29 20:19:52 +00:00

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;
}
}