diff --git a/app/Mail/PressReleasePublished.php b/app/Mail/PressReleasePublished.php index d5a5d40d..546841e0 100644 --- a/app/Mail/PressReleasePublished.php +++ b/app/Mail/PressReleasePublished.php @@ -26,10 +26,18 @@ class PressReleasePublished extends Mailable */ public array $portalLinks; + /** + * Name für die Anrede — bei API-Einlieferungen der Pressekontakt bzw. + * die Firma, nie der Kontoname des API-Einlieferers. + */ + public string $recipientName; + public function __construct( public readonly User $user, public readonly PressRelease $pressRelease, + ?string $recipientName = null, ) { + $this->recipientName = $recipientName ?? $user->name; $this->showUrl = $user->canAccessAdmin() ? route('admin.press-releases.show', $pressRelease->id) : route('me.press-releases.show', $pressRelease->id); diff --git a/app/Mail/PressReleaseRejected.php b/app/Mail/PressReleaseRejected.php index 0c9641aa..fc94d68c 100644 --- a/app/Mail/PressReleaseRejected.php +++ b/app/Mail/PressReleaseRejected.php @@ -18,11 +18,19 @@ class PressReleaseRejected extends Mailable public string $editUrl; + /** + * Name für die Anrede — bei API-Einlieferungen der Pressekontakt bzw. + * die Firma, nie der Kontoname des API-Einlieferers. + */ + public string $recipientName; + public function __construct( public readonly User $user, public readonly PressRelease $pressRelease, public readonly ?string $reason = null, + ?string $recipientName = null, ) { + $this->recipientName = $recipientName ?? $user->name; $this->editUrl = $user->canAccessAdmin() ? route('admin.press-releases.edit', $pressRelease->id) : route('me.press-releases.edit', $pressRelease->id); diff --git a/app/Services/PressRelease/PressReleaseService.php b/app/Services/PressRelease/PressReleaseService.php index 0d0be10c..ae38bb07 100644 --- a/app/Services/PressRelease/PressReleaseService.php +++ b/app/Services/PressRelease/PressReleaseService.php @@ -669,6 +669,7 @@ class PressReleaseService // ist die Quelle des letzten Review-Uebergangs im Status-Log — der // Kontotyp ist nicht verlaesslich (connektar ist z. B. 'agency'). $recipient = $user->email; + $recipientName = $user->name; $isApiSubmitter = $pressRelease->statusLogs() ->where('to_status', PressReleaseStatus::Review->value) ->orderByDesc('id') @@ -676,10 +677,28 @@ class PressReleaseService if ($isApiSubmitter) { // Scope-frei auflösen: der Mail-Versand läuft auch aus Jobs/ - // Kontexten ohne passenden Portal-Host (PortalScope). - $recipient = $pressRelease->contacts()->withoutGlobalScopes()->whereNotNull('email')->value('email') - ?? Company::withoutGlobalScopes()->find($pressRelease->company_id)?->email - ?: $user->email; + // Kontexten ohne passenden Portal-Host (PortalScope). Anrede und + // Adresse gehören zusammen — sobald die Mail an Pressekontakt + // oder Firma geht, darf nie der Name des API-Kontos gegrüßt + // werden. Ohne hinterlegten Namen bleibt $recipientName leer, + // das Template grüßt dann neutral. + $contact = $pressRelease->contacts() + ->withoutGlobalScopes() + ->whereNotNull('contacts.email') + ->where('contacts.email', '!=', '') + ->orderBy('contacts.id') + ->first(); + + $company = Company::withoutGlobalScopes()->find($pressRelease->company_id); + $companyName = trim((string) $company?->name); + + if ($contact) { + $recipient = $contact->email; + $recipientName = trim($contact->first_name.' '.$contact->last_name) ?: $companyName; + } elseif ($company && filled($company->email)) { + $recipient = $company->email; + $recipientName = $companyName; + } } // Opt-out-Gate: Benachrichtigungs-Mails sind je Typ abbestellbar @@ -701,8 +720,8 @@ class PressReleaseService } $mailable = match ($event) { - 'published' => new PressReleasePublished($user, $pressRelease), - 'rejected' => new PressReleaseRejected($user, $pressRelease, $reason), + 'published' => new PressReleasePublished($user, $pressRelease, $recipientName), + 'rejected' => new PressReleaseRejected($user, $pressRelease, $reason, $recipientName), default => null, }; diff --git a/resources/views/emails/press-release/published.blade.php b/resources/views/emails/press-release/published.blade.php index 5d30f035..3d37350d 100644 --- a/resources/views/emails/press-release/published.blade.php +++ b/resources/views/emails/press-release/published.blade.php @@ -1,5 +1,5 @@ -

Hallo {{ $user->name }},

+

{{ filled($recipientName) ? 'Hallo '.$recipientName.',' : 'Guten Tag,' }}

Ihre Pressemitteilung wurde geprüft und erfolgreich veröffentlicht.

diff --git a/resources/views/emails/press-release/rejected.blade.php b/resources/views/emails/press-release/rejected.blade.php index 525d2a61..864696a3 100644 --- a/resources/views/emails/press-release/rejected.blade.php +++ b/resources/views/emails/press-release/rejected.blade.php @@ -1,5 +1,5 @@ -

Hallo {{ $user->name }},

+

{{ filled($recipientName) ? 'Hallo '.$recipientName.',' : 'Guten Tag,' }}

Ihre eingereichte Pressemitteilung konnte leider nicht veröffentlicht werden.

@@ -8,7 +8,7 @@ @if($reason)
Ablehnungsgrund:
- {{ $reason }} + {!! nl2br(e($reason)) !!}
@else

Bitte überarbeiten Sie den Inhalt und reichen Sie die Pressemitteilung erneut ein.

diff --git a/tests/Feature/PressReleaseWorkflowTest.php b/tests/Feature/PressReleaseWorkflowTest.php index 8ec39bbc..b4caae6f 100644 --- a/tests/Feature/PressReleaseWorkflowTest.php +++ b/tests/Feature/PressReleaseWorkflowTest.php @@ -156,6 +156,23 @@ test('rejected mail uses customer edit url for customer recipients', function () }); }); +test('rejected mail renders multi-line reason with line breaks', function () { + /** @var TestCase $this */ + $author = User::factory()->create(['is_active' => true]); + $author->assignRole('customer'); + + $pr = PressRelease::factory()->create([ + 'user_id' => $author->id, + 'status' => PressReleaseStatus::Rejected->value, + ]); + + $html = (new PressReleaseRejected($author, $pr, "Erster Punkt fehlt.\nZweiter Punkt fehlt.\n\nBitte überarbeiten."))->render(); + + expect($html) + ->toContain('Erster Punkt fehlt.toContain('Zweiter Punkt fehlt.create(['is_active' => true]);