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:
Kevin Adametz 2026-10-13 10:16:21 +00:00
parent 2150d4b7ae
commit fce7baea2b
6 changed files with 419 additions and 0 deletions

View file

@ -7,6 +7,7 @@ use App\Enums\AiImageDeclaration;
use App\Services\PressRelease\ImageRightsConfirmationRequiredException;
use App\Enums\ImagePrecheckCategory;
use App\Services\Image\Precheck\ImagePrecheckReviewService;
use App\Services\PressRelease\BoostService;
use App\Services\PressRelease\Images\ImageCorrectionService;
use App\Services\PressRelease\PressReleaseService;
use App\Services\PressRelease\QuotaExceededException;
@ -116,6 +117,31 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
Flux::modal('confirm-show-archive')->close();
}
public string $boostEndReason = '';
/**
* Gebuchte Platzierung vorzeitig beenden (Rechtspaket 10/2026, U10):
* etwa nach einem Test-Boost oder wenn die Platzierung nicht mehr
* vertretbar ist. Ohne Rückbuchung; nur mit Abrechnungsrecht.
*/
public function endBoost(BoostService $boosts): void
{
$this->authorize('billing:manage');
$this->validate([
'boostEndReason' => ['required', 'string', 'min:5', 'max:490'],
]);
$ended = $boosts->endActive($this->findAdminPR($this->id), auth()->user(), trim($this->boostEndReason));
$this->boostEndReason = '';
Flux::toast(
text: $ended > 0 ? __('Gebuchte Platzierung beendet.') : __('Kein laufender Boost.'),
variant: $ended > 0 ? 'success' : 'warning',
);
}
/**
* Korrekturweg (Bildkennzeichnung E.6): Bildnachweis oder
* Portalkennzeichnung mit Pflichtgrund ändern, Bild vorläufig zurückhalten.
@ -275,6 +301,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
'latestRejection' => $latestRejection,
'coverUrl' => $cover->coverUrl($pr, 'cover'),
'coverIsPlaceholder' => $cover->coverIsPlaceholder($pr),
'activeBoosts' => $pr->boosts()->active()->orderBy('ends_at')->get(),
'categoryName' => $pr->category?->translations->firstWhere('locale', 'de')?->name
?? $pr->category?->translations->first()?->name
?? '–',
@ -489,6 +516,35 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
</article>
@endif
{{-- ============== GEBUCHTE PLATZIERUNG (BOOST) ============== --}}
@if ($activeBoosts->isNotEmpty())
<article class="panel" data-active-boosts>
<div class="panel-head">
<span class="section-eyebrow">{{ __('Gebuchte Platzierung') }}</span>
<span class="badge">{{ __('läuft') }}</span>
</div>
<div class="p-5 space-y-3 max-w-[760px]">
<ul class="m-0 list-disc pl-5 text-[13px]">
@foreach ($activeBoosts as $activeBoost)
<li>
{{ $activeBoost->placement->label() }} · {{ __(':days Tage', ['days' => $activeBoost->days]) }} · {{ __(':credits Credits', ['credits' => $activeBoost->credits_charged]) }}
<span class="text-[color:var(--color-ink-4)]">({{ $activeBoost->starts_at->format('d.m.Y H:i') }} – {{ $activeBoost->ends_at->format('d.m.Y H:i') }})</span>
</li>
@endforeach
</ul>
@can('billing:manage')
<p class="m-0 text-[12.5px] text-[color:var(--color-ink-3)]">
{{ __('Beenden nimmt die Meldung sofort von Start- und Branchenseite. Die Buchung bleibt als Nachweis erhalten; Credits werden nicht zurückgebucht.') }}
</p>
<flux:textarea wire:model="boostEndReason" :label="__('Grund (Pflicht)')" rows="2" />
<div class="flex flex-wrap gap-2">
<flux:button variant="danger" wire:click="endBoost" wire:confirm="{{ __('Gebuchte Platzierung jetzt beenden?') }}">{{ __('Boost beenden') }}</flux:button>
</div>
@endcan
</div>
</article>
@endif
{{-- ============== REJECTION-HINWEIS ============== --}}
@if ($pr->status === \App\Enums\PressReleaseStatus::Rejected && $latestRejection)
<article class="panel" style="border-color:var(--color-err); border-left-width:3px;">