*/ public function getKpis(Supervisor $supervisor): array { $offlineThresholdMinutes = (int) config('laverie.machine.offline_threshold_minutes', 10); $today = Carbon::today(); $washQuery = $this->washQuery($supervisor)->whereDate('started_at', $today); $bookingQuery = $this->bookingQuery($supervisor)->whereDate('slot_start', $today); $machineQuery = $this->machineQuery($supervisor); $offlineThreshold = now()->subMinutes($offlineThresholdMinutes); $offlineCount = (clone $machineQuery) ->where(function ($query) use ($offlineThreshold) { $query->where('status', 'offline') ->orWhere(function ($query) use ($offlineThreshold) { $query->where('last_heartbeat_at', '<', $offlineThreshold) ->orWhereNull('last_heartbeat_at'); }); }) ->count(); return [ 'revenue_today' => (float) (clone $washQuery) ->where('status', 'completed') ->sum('cost'), 'washes_today' => (clone $washQuery)->count(), 'bookings_today' => (clone $bookingQuery)->count(), 'offline_machines' => $offlineCount, 'machines_total' => (clone $machineQuery)->count(), ]; } /** * @return Collection> */ public function getAlerts(Supervisor $supervisor, int $limit = 10): Collection { $machineIds = $this->machineQuery($supervisor)->pluck('id'); if ($machineIds->isEmpty()) { return collect(); } $errorMachines = Machine::query() ->whereIn('id', $machineIds) ->whereIn('status', ['error', 'maintenance', 'offline']) ->with('establishment:id,name') ->limit($limit) ->get() ->map(fn (Machine $machine) => [ 'type' => 'machine_status', 'severity' => $machine->status === 'error' ? 'high' : 'medium', 'message' => sprintf( '%s (%s) — %s', $machine->name, $machine->establishment?->name ?? '—', $this->machineStatusLabel($machine->status), ), 'occurred_at' => $machine->updated_at?->toIso8601String(), ]); $recentEvents = MachineEvent::query() ->whereIn('machine_id', $machineIds) ->whereIn('event_type', ['error_reported', 'machine_offline', 'cycle_failed']) ->with('machine:id,name,establishment_id', 'machine.establishment:id,name') ->orderByDesc('occurred_at') ->limit($limit) ->get() ->map(fn (MachineEvent $event) => [ 'type' => 'machine_event', 'severity' => $event->event_type === 'error_reported' ? 'high' : 'medium', 'message' => sprintf( '%s — %s', $event->machine?->name ?? 'Machine', $this->eventTypeLabel($event->event_type), ), 'occurred_at' => $event->occurred_at?->toIso8601String(), ]); return $errorMachines ->concat($recentEvents) ->sortByDesc('occurred_at') ->take($limit) ->values(); } /** * @return Collection> */ public function getMachinesOverview(Supervisor $supervisor): Collection { return $this->machineQuery($supervisor) ->with('establishment:id,name') ->orderBy('name') ->limit(12) ->get() ->map(fn (Machine $machine) => [ 'uuid' => $machine->uuid, 'name' => $machine->name, 'type' => $machine->type, 'type_label' => $this->machineTypeLabel($machine->type), 'status' => $machine->status, 'status_label' => $this->machineStatusLabel($machine->status), 'establishment_name' => $machine->establishment?->name, 'last_heartbeat_at' => $machine->last_heartbeat_at?->toIso8601String(), ]); } private function machineQuery(Supervisor $supervisor) { $query = Machine::query()->whereHas('establishment', function ($query) use ($supervisor) { $query->where('organization_id', $supervisor->organization_id); if ($supervisor->establishment_id !== null) { $query->where('id', $supervisor->establishment_id); } }); if ($supervisor->establishment_id !== null) { $query->where('establishment_id', $supervisor->establishment_id); } return $query; } private function washQuery(Supervisor $supervisor) { return Wash::query()->whereHas('machine.establishment', function ($query) use ($supervisor) { $query->where('organization_id', $supervisor->organization_id); if ($supervisor->establishment_id !== null) { $query->where('id', $supervisor->establishment_id); } }); } private function bookingQuery(Supervisor $supervisor) { return Booking::query()->whereHas('machine.establishment', function ($query) use ($supervisor) { $query->where('organization_id', $supervisor->organization_id); if ($supervisor->establishment_id !== null) { $query->where('id', $supervisor->establishment_id); } }); } private function machineStatusLabel(string $status): string { return match ($status) { 'available' => 'Disponible', 'reserved' => 'Réservée', 'running' => 'En cours', 'maintenance' => 'Maintenance', 'offline' => 'Hors ligne', 'error' => 'Erreur', default => $status, }; } private function machineTypeLabel(string $type): string { return match ($type) { 'washer_small' => 'Lave-linge petit', 'washer_large' => 'Lave-linge grand', 'dryer_small' => 'Sèche-linge petit', 'dryer_large' => 'Sèche-linge grand', default => $type, }; } private function eventTypeLabel(string $type): string { return match ($type) { 'error_reported' => 'Erreur signalée', 'machine_offline' => 'Machine hors ligne', 'cycle_failed' => 'Cycle échoué', default => $type, }; } }