Files
backend/app/Services/DashboardService.php
T
2026-06-28 12:29:53 +02:00

199 lines
6.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\Booking;
use App\Models\Machine;
use App\Models\MachineEvent;
use App\Models\Supervisor;
use App\Models\Wash;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class DashboardService
{
/**
* @return array<string, mixed>
*/
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<int, array<string, mixed>>
*/
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<int, array<string, mixed>>
*/
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,
};
}
}