From f6934f54e1675b53c24ef41ceebbad439601a007 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Tue, 1 Sep 2026 11:07:23 +0000 Subject: [PATCH] =?UTF-8?q?mail:=20Anrede=20an=20den=20tats=C3=A4chlichen?= =?UTF-8?q?=20Empf=C3=A4nger=20binden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bei Einreichungen über die API geht die Veröffentlichungs- oder Ablehnungsmail nicht an das API-Konto, sondern an den Pressekontakt der Meldung oder ersatzweise an die Firmen-Adresse. Die Anrede stammte aber weiterhin vom Konto — der Empfänger wurde also mit dem Namen des Einreichers begrüßt, bei connektar mit dem der Agentur. Adresse und Name werden jetzt gemeinsam bestimmt. Ohne hinterlegten Namen bleibt er leer und das Template grüßt neutral, statt einen falschen zu raten. Co-Authored-By: Claude Opus 5 (1M context) --- app/Mail/PressReleasePublished.php | 8 +++++ app/Mail/PressReleaseRejected.php | 8 +++++ .../PressRelease/PressReleaseService.php | 31 +++++++++++++++---- .../emails/press-release/published.blade.php | 2 +- .../emails/press-release/rejected.blade.php | 4 +-- tests/Feature/PressReleaseWorkflowTest.php | 17 ++++++++++ 6 files changed, 61 insertions(+), 9 deletions(-) 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]);