status === PressReleaseStatus::Published && $pressRelease->classification === PressReleaseClassification::Green; } public function isBoosted(PressRelease $pressRelease): bool { return $pressRelease->boosts()->active()->exists(); } /** * Ende des aktuell laufenden Boosts (das späteste zukünftige `ends_at`) * oder null, wenn die PM gerade nicht geboostet ist. */ public function activeUntil(PressRelease $pressRelease): ?Carbon { $endsAt = $pressRelease->boosts()->active()->max('ends_at'); return $endsAt ? Carbon::parse($endsAt) : null; } /** * Bucht einen Boost. Wirft BoostNotAllowedException am Gate und * InvalidArgumentException bei unbekannter Laufzeit; die Wallet-Belastung * wirft InsufficientCreditsException, falls das Guthaben nicht reicht. */ public function boost(User $user, PressRelease $pressRelease, int $days): Boost { if (! $this->canBoost($pressRelease)) { throw BoostNotAllowedException::notBoostable(); } $credits = $this->pricing->boostCredits($days); return DB::transaction(function () use ($user, $pressRelease, $days, $credits): Boost { $startsAt = $this->activeUntil($pressRelease) ?? now(); $boost = $pressRelease->boosts()->create([ 'user_id' => $user->id, 'days' => $days, 'credits_charged' => $credits, 'starts_at' => $startsAt, 'ends_at' => $startsAt->copy()->addDays($days), ]); $this->wallet->debit( $user, $credits, "Boost {$days} Tage – PM #{$pressRelease->id}", $boost, ); return $boost; }); } }