*/ public function ticketsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $base = Ticket::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('issued_at', [$from, $to]); $waitDiff = DatabaseTime::diffSeconds('issued_at', 'called_at'); $serviceDiff = DatabaseTime::diffSeconds('serving_started_at', 'completed_at'); return [ 'total_issued' => (clone $base)->count(), 'completed' => (clone $base)->where('status', 'completed')->count(), 'no_show' => (clone $base)->where('status', 'no_show')->count(), 'cancelled' => (clone $base)->where('status', 'cancelled')->count(), 'avg_wait_seconds' => (int) round((float) (clone $base) ->where('status', 'completed') ->whereNotNull('called_at') ->selectRaw("AVG({$waitDiff}) as v") ->value('v')), 'avg_service_seconds' => (int) round((float) (clone $base) ->where('status', 'completed') ->whereNotNull('serving_started_at') ->whereNotNull('completed_at') ->selectRaw("AVG({$serviceDiff}) as v") ->value('v')), ]; } /** * @return array */ public function waitTimesReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $hourExpr = DatabaseTime::hourOf('issued_at'); $waitDiff = DatabaseTime::diffSeconds('issued_at', 'called_at'); $rows = Ticket::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('issued_at', [$from, $to]) ->where('status', 'completed') ->whereNotNull('called_at') ->selectRaw("{$hourExpr} as hour, AVG({$waitDiff}) as avg_wait") ->groupBy('hour') ->orderBy('hour') ->get(); $peak = $rows->sortByDesc('avg_wait')->first(); return [ 'hourly' => $rows->map(fn ($r) => ['hour' => (int) $r->hour, 'avg_wait_seconds' => (int) round((float) $r->avg_wait)])->values()->all(), 'peak_hour' => $peak ? (int) $peak->hour : null, 'peak_avg_wait_seconds' => $peak ? (int) round((float) $peak->avg_wait) : 0, ]; } /** * @return array */ public function countersReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $counters = Counter::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->withCount(['tickets as served_count' => function ($q) use ($from, $to) { $q->where('status', 'completed') ->whereBetween('completed_at', [$from, $to]); }]) ->orderByDesc('served_count') ->get(); return [ 'counters' => $counters->map(fn ($c) => [ 'name' => $c->name, 'served' => $c->served_count, ])->all(), ]; } /** * @return array */ public function appointmentsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $base = QueueAppointment::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('scheduled_at', [$from, $to]); return [ 'total' => (clone $base)->count(), 'checked_in' => (clone $base)->where('status', 'checked_in')->count(), 'completed' => (clone $base)->where('status', 'completed')->count(), 'cancelled' => (clone $base)->where('status', 'cancelled')->count(), 'no_show' => (clone $base)->where('status', 'no_show')->count(), ]; } /** * @return array */ public function feedbackReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $base = CustomerFeedback::owned($ownerRef) ->where('organization_id', $organizationId) ->whereBetween('created_at', [$from, $to]) ->when($branchId, function ($q) use ($branchId) { $q->whereHas('ticket', fn ($t) => $t->where('branch_id', $branchId)); }); $count = (clone $base)->count(); $avgRating = (clone $base)->avg('rating'); return [ 'responses' => $count, 'avg_rating' => $count ? round((float) $avgRating, 1) : 0, 'avg_service_quality' => round((float) (clone $base)->avg('service_quality'), 1), 'avg_wait_experience' => round((float) (clone $base)->avg('wait_experience'), 1), 'avg_staff_professionalism' => round((float) (clone $base)->avg('staff_professionalism'), 1), ]; } /** * @return array */ public function serviceSessionsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $base = ServiceSession::owned($ownerRef) ->whereBetween('started_at', [$from, $to]) ->whereHas('ticket', function ($q) use ($organizationId, $branchId) { $q->where('organization_id', $organizationId); if ($branchId) { $q->where('branch_id', $branchId); } }); $completed = (clone $base)->whereNotNull('ended_at'); return [ 'sessions' => (clone $base)->count(), 'completed_sessions' => (clone $completed)->count(), 'avg_duration_seconds' => (int) round((float) (clone $completed)->avg('duration_seconds')), 'total_service_seconds' => (int) (clone $completed)->sum('duration_seconds'), ]; } }