findNextAvailableVmid(); VmidReservation::query()->create([ 'vmid' => $vmid, 'customer_id' => $customer->id, 'status' => 'reserved', ]); return $vmid; }); } public function activate(int $vmid, Customer $customer): void { VmidReservation::query() ->where('vmid', $vmid) ->where('customer_id', $customer->id) ->update([ 'status' => 'active', 'release_at' => null, ]); } public function scheduleRelease(int $vmid, ?Customer $customer = null): void { $hours = (int) config('hosting.vmid.release_after_hours', 48); $query = VmidReservation::query()->where('vmid', $vmid); if ($customer) { $query->where('customer_id', $customer->id); } $query->update([ 'status' => 'pending_release', 'release_at' => now()->addHours($hours), ]); } public function releaseDue(): int { $count = 0; $due = VmidReservation::query() ->where('status', 'pending_release') ->where('release_at', '<=', now()) ->get(); foreach ($due as $reservation) { $reservation->update([ 'status' => 'released', 'released_at' => now(), ]); $count++; } return $count; } public function isVmidBlocked(int $vmid): bool { return VmidReservation::query() ->where('vmid', $vmid) ->whereIn('status', ['reserved', 'active', 'pending_release']) ->exists(); } private function findNextAvailableVmid(): int { $start = (int) config('hosting.vmid.range_start', 2000); $end = (int) config('hosting.vmid.range_end', 2999); $blocked = VmidReservation::query() ->whereIn('status', ['reserved', 'active', 'pending_release']) ->pluck('vmid') ->flip() ->all(); for ($vmid = $start; $vmid <= $end; $vmid++) { if (! isset($blocked[$vmid])) { return $vmid; } } throw new ProvisioningException( "No free VMID in range {$start}-{$end}.", step: 'reserving_vmid', ); } }