toImmutable(); $guestCutoff = $closedAt->subDays(config('rooms.guest_retention_days')); $accountCutoff = $closedAt->subDays(config('rooms.account_retention_days')); Room::query() ->whereNull('closed_at') ->where('is_persistent', false) ->where(function ($query) use ($guestCutoff, $accountCutoff): void { $query ->where(function ($query) use ($guestCutoff): void { $query->whereNull('owner_id') ->where('last_activity_at', '<=', $guestCutoff); }) ->orWhere(function ($query) use ($accountCutoff): void { $query->whereNotNull('owner_id') ->where('last_activity_at', '<=', $accountCutoff); }); }) ->select('id') ->chunkById(100, function ($rooms) use ($closedAt, $guestCutoff, $accountCutoff): void { foreach ($rooms->pluck('id') as $roomId) { $stateVersion = DB::transaction(function () use ($roomId, $closedAt, $guestCutoff, $accountCutoff): ?int { $room = Room::query() ->lockForUpdate() ->find((string) $roomId); if ( $room === null || $room->isClosed() || $room->is_persistent || $room->last_activity_at === null ) { return null; } $cutoff = $room->owner_id === null ? $guestCutoff : $accountCutoff; if ($room->last_activity_at->greaterThan($cutoff)) { return null; } $room->update([ 'closed_at' => $closedAt, 'state_version' => $room->state_version + 1, ]); return $room->state_version; }); if ($stateVersion !== null) { RoomStateChanged::dispatch( (string) $roomId, $stateVersion, RoomStateChange::RoomClosed, ); } } }, 'id'); } }