where('status', UserPaymentOptionStatus::Grandfathered->value) ->whereNull('cancelled_at') ->whereNotNull('grandfathered_until') ->whereDate('grandfathered_until', '<', today()) ->with('user') ->get(); if ($expired->isEmpty()) { $this->info('Kein Bestandsschutz mit überschrittenem Vertragsende.'); return self::SUCCESS; } if ($this->option('dry-run')) { foreach ($expired as $option) { $this->line(sprintf('[dry-run] Posten #%d (User #%s) endete am %s', $option->id, $option->user_id, $option->grandfathered_until?->toDateString())); } return self::SUCCESS; } $ended = 0; foreach ($expired->groupBy('user_id') as $userId => $options) { /** @var User|null $user */ $user = $options->first()?->user; DB::transaction(function () use ($options, &$ended): void { $ended += UserPaymentOption::query() ->whereIn('id', $options->pluck('id')->all()) ->whereNull('cancelled_at') ->update([ 'status' => UserPaymentOptionStatus::Cancelled->value, 'cancelled_at' => now(), ]); }); if ($user === null) { continue; } $latestEnd = $options->max(fn (UserPaymentOption $option) => $option->grandfathered_until); $activityLog->record( ActivityType::SubscriptionChange, sprintf('Bestandsschutz zum Vertragsende %s beendet (%d Posten).', $latestEnd?->format('d.m.Y'), $options->count()), $user, null, ['action' => 'expire_grandfathering', 'posts' => $options->count(), 'until' => $latestEnd?->toDateString()], 'system', ); if (! $suppressions->isSuppressed($user->email)) { Mail::to($user)->queue(new GrandfatheringExpired($user, $latestEnd)); } } $this->info(sprintf('%d Bestandsschutz-Posten beendet.', $ended)); return self::SUCCESS; } }