initial commit
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
<?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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user