feat: complete reservation lifecycle

This commit is contained in:
2026-08-04 16:30:11 +01:00
parent 077015f17a
commit e667d05bc1
24 changed files with 1096 additions and 46 deletions

View File

@@ -3,23 +3,36 @@
namespace App\Http\Controllers;
use App\Domain\Inventory\Models\SnipeAsset;
use App\Domain\Reservations\Services\AvailabilityService;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class AssetController extends Controller
{
public function index(Request $request): Response
public function index(Request $request, AvailabilityService $availability): Response
{
$filters = $request->validate([
'search' => ['nullable', 'string', 'max:100'],
'category' => ['nullable', 'string', 'max:100'],
'location' => ['nullable', 'string', 'max:100'],
'starts_at' => ['nullable', 'date', 'required_with:ends_at', 'after:now'],
'ends_at' => ['nullable', 'date', 'required_with:starts_at', 'after:starts_at'],
]);
$assets = SnipeAsset::query()
->where('active', true)
->where('requestable', true)
$periodSelected = isset($filters['starts_at'], $filters['ends_at']);
$query = $periodSelected
? $availability->availableAssets(
CarbonImmutable::parse($filters['starts_at']),
CarbonImmutable::parse($filters['ends_at']),
)
: SnipeAsset::query()
->where('active', true)
->where('requestable', true)
->whereNull('assigned_id');
$assets = $query
->when($filters['search'] ?? null, function ($query, string $search) {
$query->where(function ($query) use ($search) {
$query->where('name', 'like', "%{$search}%")
@@ -36,8 +49,9 @@ class AssetController extends Controller
return Inertia::render('assets/index', [
'assets' => $assets,
'filters' => $filters,
'categories' => SnipeAsset::query()->where('active', true)->whereNotNull('category_name')->distinct()->orderBy('category_name')->pluck('category_name'),
'locations' => SnipeAsset::query()->where('active', true)->whereNotNull('location_name')->distinct()->orderBy('location_name')->pluck('location_name'),
'periodSelected' => $periodSelected,
'categories' => SnipeAsset::query()->where('active', true)->where('requestable', true)->whereNotNull('category_name')->distinct()->orderBy('category_name')->pluck('category_name'),
'locations' => SnipeAsset::query()->where('active', true)->where('requestable', true)->whereNotNull('location_name')->distinct()->orderBy('location_name')->pluck('location_name'),
'lastSyncedAt' => SnipeAsset::max('synced_at'),
]);
}

View File

@@ -3,8 +3,11 @@
namespace App\Http\Controllers;
use App\Domain\Inventory\Models\SnipeAsset;
use App\Domain\Reservations\Models\Reservation;
use App\Domain\Reservations\Services\AvailabilityService;
use App\Domain\Reservations\Services\ReservationService;
use App\Http\Requests\StoreReservationRequest;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
@@ -22,16 +25,54 @@ class ReservationController extends Controller
]);
}
public function create(Request $request): Response
public function calendar(Request $request): Response
{
return Inertia::render('reservations/create', [
'assets' => SnipeAsset::query()
$filters = $request->validate([
'month' => ['nullable', 'date_format:Y-m'],
]);
$month = isset($filters['month'])
? CarbonImmutable::createFromFormat('!Y-m', $filters['month'])
: now()->startOfMonth();
$windowEnd = $month->addMonth();
return Inertia::render('reservations/calendar', [
'month' => $month->format('Y-m'),
'reservations' => $request->user()->reservations()
->with('items.asset:id,name,asset_tag')
->where('starts_at', '<', $windowEnd)
->where('ends_at', '>', $month)
->orderBy('starts_at')
->get(),
]);
}
public function create(Request $request, AvailabilityService $availability): Response
{
$period = $request->validate([
'starts_at' => ['nullable', 'date', 'required_with:ends_at', 'after:now'],
'ends_at' => ['nullable', 'date', 'required_with:starts_at', 'after:starts_at'],
]);
$periodSelected = isset($period['starts_at'], $period['ends_at']);
$query = $periodSelected
? $availability->availableAssets(
CarbonImmutable::parse($period['starts_at']),
CarbonImmutable::parse($period['ends_at']),
)
: SnipeAsset::query()
->where('active', true)
->where('requestable', true)
->whereNull('assigned_id')
->orderBy('name')
->get(['id', 'name', 'asset_tag', 'model_name', 'category_name', 'location_name']),
'selectedAssetId' => $request->integer('asset'),
->whereNull('assigned_id');
$assets = $query
->orderBy('name')
->get(['id', 'name', 'asset_tag', 'model_name', 'category_name', 'location_name']);
$requestedAssetId = $request->integer('asset');
return Inertia::render('reservations/create', [
'assets' => $assets,
'selectedAssetId' => $assets->contains('id', $requestedAssetId) ? $requestedAssetId : 0,
'period' => $period,
'periodSelected' => $periodSelected,
]);
}
@@ -43,4 +84,15 @@ class ReservationController extends Controller
return to_route('dashboard')->with('success', "Requisição {$reservation->reference} submetida para aprovação.");
}
public function cancel(Request $request, Reservation $reservation, ReservationService $service): RedirectResponse
{
$data = $request->validate([
'reason' => ['required', 'string', 'min:5', 'max:1000'],
]);
$service->cancel($reservation, $request->user(), $data['reason']);
return back()->with('success', "Requisição {$reservation->reference} cancelada. Os equipamentos ficaram novamente disponíveis.");
}
}