press-releases: Mail "wird persoenlich geprueft" beim Block, Blade-Guard fuer @php-Bloecke (Roadmap 1d, Nachtrag)

Der Autor bekommt beim Block nach dem letzten Rot (oder Gelb mit
eingeschalteter Queue) eine kurze Mail statt Stillstand: was passiert ist,
dass nichts zu tun ist, und dass das Ergebnis per Mail kommt. Laeuft ueber
notifyAuthor() wie Veroeffentlichung und Ablehnung (API-Regel, Sperrliste,
adressbezogene Abmeldung, Bounce-Referenz); Opt-out am Typ "abgelehnt".
Neue ConversionSource mail_review fuer den Link ins Konto.

BladePhpBlockGuardTest wendet das Compiler-Muster auf alle Views an und
faengt inline @php( vor einem spaeteren @endphp ab — fand den eigenen
Erklaer-Kommentar im Modal, der nur zufaellig funktionierte. Regel in
CLAUDE.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-16 15:05:47 +00:00
parent 35db30e019
commit 4fd1db7825
11 changed files with 278 additions and 24 deletions

View file

@ -9,6 +9,7 @@ use App\Enums\PressReleaseStatus;
use App\Enums\SinglePurchaseStatus;
use App\Jobs\ClassifyPressRelease;
use App\Jobs\ScorePressRelease;
use App\Mail\PressReleaseHeldForReview;
use App\Mail\PressReleasePublished;
use App\Mail\PressReleaseRejected;
use App\Models\AdminPreset;
@ -209,7 +210,7 @@ class PressReleaseService
$pressRelease->auto_check_attempts,
$max,
$reason ?: 'Rot ohne Begründung.',
));
), automationExhausted: true);
return;
}
@ -222,7 +223,7 @@ class PressReleaseService
// Gelb geht durch (Entscheidung 16.09.2026) — außer der Schalter für
// die manuelle Queue ist bewusst eingeschaltet.
if ($classification === PressReleaseClassification::Yellow && config('scoring.classification.yellow_to_manual_queue')) {
$this->holdForManualReview($pressRelease, 'Gelb — manuelle Prüfung laut Konfiguration.');
$this->holdForManualReview($pressRelease, 'Gelb — manuelle Prüfung laut Konfiguration.', automationExhausted: false);
return;
}
@ -232,10 +233,11 @@ class PressReleaseService
/**
* Meldung bleibt in `review`, wird als „Prüfung nötig" markiert und
* wartet auf die Entscheidung im Admin. Kein Slot-Verbrauch, keine Mail
* an den Autor — der sieht den Stand in seinem Konto.
* wartet auf die Entscheidung im Admin. Kein Slot-Verbrauch. Der Autor
* bekommt eine kurze Mail „ein Mensch prüft jetzt" — ohne sie sähe er
* nach dem letzten Rot nur Stillstand statt einer Ablehnung.
*/
private function holdForManualReview(PressRelease $pressRelease, string $reason): void
private function holdForManualReview(PressRelease $pressRelease, string $reason, bool $automationExhausted): void
{
$pressRelease->update(['manual_review_required_at' => now()]);
$this->logStatusChange($pressRelease, PressReleaseStatus::Review, PressReleaseStatus::Review, $reason, 'ki');
@ -246,6 +248,8 @@ class PressReleaseService
subject: $pressRelease,
actorLabel: 'ki',
);
$this->notifyAuthor($pressRelease, 'held', automationExhausted: $automationExhausted);
}
/**
@ -723,7 +727,7 @@ class PressReleaseService
$this->adminCache->forgetPressReleaseStats();
}
private function notifyAuthor(PressRelease $pressRelease, string $event, ?string $reason = null): void
private function notifyAuthor(PressRelease $pressRelease, string $event, ?string $reason = null, bool $automationExhausted = false): void
{
/** @var User|null $user */
$user = $pressRelease->user;
@ -778,10 +782,11 @@ class PressReleaseService
// Opt-out-Gate: Benachrichtigungs-Mails sind je Typ abbestellbar
// (One-Click-Unsubscribe aus der Mail bzw. Präferenzen im Profil).
// Greift nur, wenn der Einlieferer selbst der Empfänger ist —
// Pressekontakt/Firma haben kein Konto mit Präferenzen.
// Pressekontakt/Firma haben kein Konto mit Präferenzen. Der
// Zwischenstand „ein Mensch prüft" hängt am Typ „abgelehnt".
$notificationType = match ($event) {
'published' => EmailNotificationType::PressReleasePublished,
'rejected' => EmailNotificationType::PressReleaseRejected,
'rejected', 'held' => EmailNotificationType::PressReleaseRejected,
default => null,
};
@ -793,9 +798,11 @@ class PressReleaseService
return;
}
$mailable = $notificationType === EmailNotificationType::PressReleasePublished
? new PressReleasePublished($user, $pressRelease, $recipientName, $recipient)
: new PressReleaseRejected($user, $pressRelease, $reason, $recipientName, $recipient);
$mailable = match ($event) {
'published' => new PressReleasePublished($user, $pressRelease, $recipientName, $recipient),
'held' => new PressReleaseHeldForReview($user, $pressRelease, $automationExhausted, $recipientName, $recipient),
default => new PressReleaseRejected($user, $pressRelease, $reason, $recipientName, $recipient),
};
Mail::to($recipient)->queue($mailable);
}