feat: complete equipment handover workflow

This commit is contained in:
2026-08-04 17:15:51 +01:00
parent 809eab9d4a
commit d829f34580
24 changed files with 1800 additions and 28 deletions

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Http\Controllers;
use App\Domain\Operations\Services\HandoverService;
use App\Domain\Reservations\Enums\ReservationStatus;
use App\Domain\Reservations\Models\Reservation;
use App\Domain\Reservations\Models\ReservationItem;
use App\Integrations\SnipeIt\SnipeItClient;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class OperationController extends Controller
{
public function index(Request $request, SnipeItClient $snipeIt): Response
{
$start = now()->startOfDay();
$end = now()->addDay()->startOfDay();
return Inertia::render('operations/index', [
'operationallyConfigured' => $snipeIt->isOperationallyConfigured(),
'reservations' => Reservation::query()
->with([
'requester:id,name,email,department,snipe_user_id',
'items.asset:id,snipe_asset_id,name,asset_tag,status_id,status_name,location_id,location_name',
'items.handoverRecords:id,reservation_item_id,type,operator_id,condition,notes,created_at',
'items.handoverRecords.operator:id,name',
])
->whereIn('status', [
ReservationStatus::Approved,
ReservationStatus::Ready,
ReservationStatus::Delivered,
ReservationStatus::InUse,
ReservationStatus::Returned,
ReservationStatus::Overdue,
ReservationStatus::Incident,
])
->where(function ($query) use ($start, $end) {
$query->where(function ($query) use ($start, $end) {
$query->where('starts_at', '<', $end)->where('ends_at', '>', $start);
})->orWhereIn('status', [
ReservationStatus::Delivered,
ReservationStatus::InUse,
ReservationStatus::Returned,
ReservationStatus::Overdue,
ReservationStatus::Incident,
]);
})
->orderByRaw("CASE WHEN status IN ('overdue', 'incident') THEN 0 ELSE 1 END")
->orderBy('starts_at')
->limit(50)
->get(),
]);
}
public function prepare(Request $request, Reservation $reservation, HandoverService $service): RedirectResponse
{
$service->markReady($reservation, $request->user());
return back()->with('success', "Requisição {$reservation->reference} preparada.");
}
public function deliver(Request $request, Reservation $reservation, HandoverService $service): RedirectResponse
{
$data = $request->validate([
'snipe_user_id' => ['required', 'integer', 'min:1'],
'condition' => ['required', 'string', 'min:3', 'max:500'],
'notes' => ['nullable', 'string', 'max:1000'],
]);
$service->deliver(
$reservation,
$request->user(),
(int) $data['snipe_user_id'],
$data['condition'],
$data['notes'] ?? null,
);
return back()->with('success', "Entrega de {$reservation->reference} registada no portal e no Snipe-IT.");
}
public function returnEquipment(Request $request, Reservation $reservation, HandoverService $service): RedirectResponse
{
$data = $request->validate([
'condition' => ['required', 'string', 'min:3', 'max:500'],
'notes' => ['nullable', 'string', 'max:1000'],
]);
$service->returnEquipment($reservation, $request->user(), $data['condition'], $data['notes'] ?? null);
return back()->with('success', "Devolução de {$reservation->reference} registada no portal e no Snipe-IT.");
}
public function complete(Request $request, Reservation $reservation, HandoverService $service): RedirectResponse
{
$service->complete($reservation, $request->user());
return back()->with('success', "Requisição {$reservation->reference} concluída.");
}
public function noShow(Request $request, Reservation $reservation, HandoverService $service): RedirectResponse
{
$data = $request->validate(['notes' => ['required', 'string', 'min:3', 'max:1000']]);
$service->markNoShow($reservation, $request->user(), $data['notes']);
return back()->with('success', "Falta registada em {$reservation->reference}.");
}
public function overdue(Request $request, Reservation $reservation, HandoverService $service): RedirectResponse
{
$data = $request->validate(['notes' => ['required', 'string', 'min:3', 'max:1000']]);
$service->markOverdue($reservation, $request->user(), $data['notes']);
return back()->with('success', "Atraso registado em {$reservation->reference}.");
}
public function incident(
Request $request,
Reservation $reservation,
ReservationItem $item,
HandoverService $service,
): RedirectResponse {
$data = $request->validate([
'notes' => ['required', 'string', 'min:5', 'max:1000'],
'blocked_until' => ['nullable', 'date', 'after:now'],
]);
$service->reportIncident(
$reservation,
$item,
$request->user(),
$data['notes'],
isset($data['blocked_until']) ? CarbonImmutable::parse($data['blocked_until']) : null,
);
return back()->with('success', "Incidente registado em {$reservation->reference}.");
}
}

View File

@@ -48,6 +48,7 @@ class HandleInertiaRequests extends Middleware
'can' => [
'administer' => fn (): bool => $request->user()?->can('administer') ?? false,
'approveReservations' => fn (): bool => $request->user()?->can('approve-reservations') ?? false,
'operateInventory' => fn (): bool => $request->user()?->can('operate-inventory') ?? false,
],
'unreadNotifications' => fn (): int => $request->user()?->unreadNotifications()->count() ?? 0,
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',