admin: Boost vorzeitig beenden, Berechtigungsliste audit:admins
Admin ▸ Pressemitteilung zeigt laufende Boosts; „Boost beenden“ verlangt einen Grund und billing:manage, beendet laufende und angeschlossene Zeiträume, verwirft die Seiten-Caches und protokolliert „Boost beendet“. Credits werden nicht zurückgebucht. audit:admins listet alle Konten mit Rechten über den Kundenbereich hinaus (Super-Admin, Rollen außer customer/api-only, direkte Permissions), auch gesperrte und gelöschte, als Markdown mit 2FA, Passkeys, Tokens und letztem Sitzungswiderruf. E-Mail standardmäßig nur mit Domain (Nachweis BN-02). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2150d4b7ae
commit
fce7baea2b
6 changed files with 419 additions and 0 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Services\PressRelease;
|
||||
|
||||
use App\Enums\ActivityType;
|
||||
use App\Enums\BoostPlacement;
|
||||
use App\Enums\Portal;
|
||||
use App\Enums\PressReleaseClassification;
|
||||
|
|
@ -11,6 +12,7 @@ use App\Models\Boost;
|
|||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use App\Scopes\PortalScope;
|
||||
use App\Services\ActivityLog;
|
||||
use App\Services\Billing\CreditPricingService;
|
||||
use App\Services\Billing\CreditWalletService;
|
||||
use App\Support\WebPageCache;
|
||||
|
|
@ -35,6 +37,7 @@ class BoostService
|
|||
public function __construct(
|
||||
private readonly CreditPricingService $pricing,
|
||||
private readonly CreditWalletService $wallet,
|
||||
private readonly ActivityLog $activityLog,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
@ -180,6 +183,52 @@ class BoostService
|
|||
return $boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beendet alle laufenden und bereits angeschlossenen Boosts einer PM
|
||||
* sofort (Admin ▸ Pressemitteilung), etwa nach einer Test-Buchung oder
|
||||
* wenn die Platzierung nicht mehr vertretbar ist. Die Zeilen bleiben als
|
||||
* Buchungsnachweis erhalten; Credits werden nicht zurückgebucht —
|
||||
* Erstattungen laufen getrennt.
|
||||
*
|
||||
* @return int Anzahl der beendeten Boosts
|
||||
*/
|
||||
public function endActive(PressRelease $pressRelease, User $endedBy, string $reason): int
|
||||
{
|
||||
$now = now();
|
||||
$boosts = $pressRelease->boosts()->active()->get();
|
||||
|
||||
if ($boosts->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach ($boosts as $boost) {
|
||||
$boost->update([
|
||||
'starts_at' => $boost->starts_at->isFuture() ? $now : $boost->starts_at,
|
||||
'ends_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
$boosts->pluck('placement')->unique()->each(
|
||||
fn (BoostPlacement $placement) => $this->forgetPlacementCaches($pressRelease, $placement),
|
||||
);
|
||||
|
||||
$this->activityLog->record(
|
||||
ActivityType::BoostEnded,
|
||||
sprintf(
|
||||
'%s hat %d gebuchte Platzierung(en) von PM #%d vorzeitig beendet. Grund: %s',
|
||||
$endedBy->name,
|
||||
$boosts->count(),
|
||||
$pressRelease->id,
|
||||
$reason,
|
||||
),
|
||||
user: $endedBy,
|
||||
subject: $pressRelease,
|
||||
meta: ['boost_ids' => $boosts->modelKeys(), 'ended_by' => $endedBy->id, 'reason' => $reason],
|
||||
);
|
||||
|
||||
return $boosts->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialisiert Käufe, die dasselbe sichtbare Slot-Inventar verändern.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue