Files
reservas/app/Http/Controllers/ReportController.php

110 lines
5.0 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Domain\Audit\Models\AuditLog;
use App\Domain\Reservations\Enums\ReservationItemStatus;
use App\Domain\Reservations\Enums\ReservationStatus;
use App\Domain\Reservations\Models\Reservation;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ReportController extends Controller
{
public function index(Request $request): Response
{
[$filters, $from, $to] = $this->period($request);
$reservations = $this->query($from, $to)->get();
$items = $reservations->flatMap->items->where('status', '!=', ReservationItemStatus::Cancelled);
return Inertia::render('reports/index', [
'filters' => $filters,
'summary' => [
'total' => $reservations->count(),
'completed' => $reservations->where('status', ReservationStatus::Completed)->count(),
'incidents' => AuditLog::query()->where('event', 'reservation.incident_reported')->whereBetween('created_at', [$from, $to])->count(),
'overdue' => AuditLog::query()->where('event', 'reservation.overdue')->whereBetween('created_at', [$from, $to])->count(),
'utilizationHours' => round($items->sum(function ($item): float {
$end = $item->returned_at ?? ($item->ends_at->isPast() ? $item->ends_at : now());
return $item->delivered_at === null ? 0 : max(0, $item->delivered_at->diffInMinutes($end) / 60);
}), 1),
],
'byStatus' => $reservations->countBy(fn ($reservation): string => $reservation->status->value)->sortDesc(),
'byCategory' => $items->countBy(fn ($item): string => $item->asset->category_name ?? 'Sem categoria')->sortDesc()->take(10),
'byDepartment' => $reservations->countBy(fn ($reservation): string => $reservation->requester->department ?? 'Sem departamento')->sortDesc()->take(10),
'topAssets' => $items->countBy(fn ($item): string => "{$item->asset->asset_tag} — {$item->asset->name}")->sortDesc()->take(10),
]);
}
public function export(Request $request): StreamedResponse
{
[$filters, $from, $to] = $this->period($request);
$reservations = $this->query($from, $to)->lazy(200);
return response()->streamDownload(function () use ($reservations): void {
$output = fopen('php://output', 'w');
if ($output === false) {
return;
}
fwrite($output, "\xEF\xBB\xBF");
fputcsv($output, ['Referência', 'Requisitante', 'Departamento', 'Estado', 'Início', 'Fim', 'Equipamentos'], ';', '"', '');
foreach ($reservations as $reservation) {
fputcsv($output, [
$this->csvCell($reservation->reference),
$this->csvCell($reservation->requester->name),
$this->csvCell($reservation->requester->department),
$this->csvCell($reservation->status->value),
$reservation->starts_at->toIso8601String(),
$reservation->ends_at->toIso8601String(),
$this->csvCell($reservation->items->pluck('asset.asset_tag')->join(', ')),
], ';', '"', '');
}
fclose($output);
}, "reservas-{$filters['date_from']}-{$filters['date_to']}.csv", [
'Content-Type' => 'text/csv; charset=UTF-8',
]);
}
/** @return array{array{date_from:string,date_to:string},CarbonImmutable,CarbonImmutable} */
private function period(Request $request): array
{
$filters = $request->validate([
'date_from' => ['nullable', 'date'],
'date_to' => ['nullable', 'date', 'after_or_equal:date_from'],
]);
$from = isset($filters['date_from']) ? CarbonImmutable::parse($filters['date_from'])->startOfDay() : now()->subDays(29)->startOfDay();
$to = isset($filters['date_to']) ? CarbonImmutable::parse($filters['date_to'])->endOfDay() : now()->endOfDay();
if ($from->diffInDays($to) > 366) {
throw ValidationException::withMessages(['date_to' => 'O relatório está limitado a um período máximo de 366 dias.']);
}
return [[
'date_from' => $from->toDateString(),
'date_to' => $to->toDateString(),
], $from, $to];
}
/** @return Builder<Reservation> */
private function query(CarbonImmutable $from, CarbonImmutable $to): Builder
{
return Reservation::query()
->with(['requester:id,name,department', 'items.asset:id,name,asset_tag,category_name'])
->where('starts_at', '<=', $to)
->where('ends_at', '>=', $from)
->orderBy('starts_at');
}
private function csvCell(?string $value): string
{
$value ??= '';
return preg_match('/^\s*[=+\-@]/', $value) === 1 ? "'{$value}" : $value;
}
}