110 lines
5.0 KiB
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;
|
|
}
|
|
}
|