diff --git a/app/Models/PressRelease.php b/app/Models/PressRelease.php index eaada7b4..d2ce68d6 100644 --- a/app/Models/PressRelease.php +++ b/app/Models/PressRelease.php @@ -83,6 +83,7 @@ class PressRelease extends Model 'published_at', 'scheduled_at', 'embargo_at', + 'withdrawal_waiver_consented_at', 'legacy_portal', 'legacy_id', ]; @@ -105,6 +106,7 @@ class PressRelease extends Model 'published_at' => 'datetime', 'scheduled_at' => 'datetime', 'embargo_at' => 'datetime', + 'withdrawal_waiver_consented_at' => 'datetime', 'deleted_at' => 'datetime', ]; } diff --git a/app/Services/PressRelease/PressReleaseService.php b/app/Services/PressRelease/PressReleaseService.php index a496d898..3ce89e3b 100644 --- a/app/Services/PressRelease/PressReleaseService.php +++ b/app/Services/PressRelease/PressReleaseService.php @@ -64,7 +64,13 @@ class PressReleaseService throw new BlacklistViolationException($reason, $word); } - $pressRelease->update(['status' => PressReleaseStatus::Review->value]); + // Erlöschensklausel (§ 356 Abs. 4/5 BGB): Das Einreichungs-Modal + // verlangt die Bestätigung der sofortigen Ausführung — der Zeitpunkt + // wird hier zur Nachweisbarkeit protokolliert. + $pressRelease->update([ + 'status' => PressReleaseStatus::Review->value, + 'withdrawal_waiver_consented_at' => now(), + ]); $this->logStatusChange($pressRelease, $previous, PressReleaseStatus::Review, null, 'customer'); // KI-Klassifikation asynchron anstoßen (Red Flag). Das Routing anhand diff --git a/database/migrations/2026_07_03_133618_add_withdrawal_waiver_to_press_releases.php b/database/migrations/2026_07_03_133618_add_withdrawal_waiver_to_press_releases.php new file mode 100644 index 00000000..66945d1b --- /dev/null +++ b/database/migrations/2026_07_03_133618_add_withdrawal_waiver_to_press_releases.php @@ -0,0 +1,29 @@ +timestamp('withdrawal_waiver_consented_at')->nullable()->after('embargo_at'); + }); + } + + public function down(): void + { + Schema::table('press_releases', function (Blueprint $table) { + $table->dropColumn('withdrawal_waiver_consented_at'); + }); + } +}; diff --git a/resources/views/components/press-release-submit-modal.blade.php b/resources/views/components/press-release-submit-modal.blade.php index eba4d780..b6b1238c 100644 --- a/resources/views/components/press-release-submit-modal.blade.php +++ b/resources/views/components/press-release-submit-modal.blade.php @@ -62,7 +62,7 @@ @else -
+
{{ __('Veröffentlichung') }} {{ __('Pressemitteilung zur Prüfung einreichen') }} @@ -113,6 +113,13 @@ x-on:change="contact = $event.target.checked" :label="__('Die Angaben zum Pressekontakt sind korrekt.')" /> + {{-- Erlöschensklausel (§ 356 Abs. 4/5 BGB): Bestätigungszeitpunkt + wird serverseitig protokolliert (PressReleaseService). --}} +
@@ -121,7 +128,7 @@ + x-bind:disabled="! (agb && images && contact && waiver)"> {{ $confirmLabel ?? __('Veröffentlichung anfordern') }}
diff --git a/resources/views/livewire/customer/press-releases/index.blade.php b/resources/views/livewire/customer/press-releases/index.blade.php index 2c5dd591..20221df1 100644 --- a/resources/views/livewire/customer/press-releases/index.blade.php +++ b/resources/views/livewire/customer/press-releases/index.blade.php @@ -454,12 +454,19 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
{{ $pr->status->label() }} @if ($status === 'draft') - + {{-- Öffnet das gemeinsame Einreichungs-Modal (rechtliche + Bestätigungen inkl. Erlöschensklausel) statt eines + Schnell-Submits, der die Bestätigungen umgehen würde. --}} + + + + @endif
diff --git a/tests/Feature/PressReleaseWithdrawalWaiverTest.php b/tests/Feature/PressReleaseWithdrawalWaiverTest.php new file mode 100644 index 00000000..81ee7171 --- /dev/null +++ b/tests/Feature/PressReleaseWithdrawalWaiverTest.php @@ -0,0 +1,57 @@ +seed(RolesAndPermissionsSeeder::class); + + Queue::fake(); +}); + +test('the submit modal requires the withdrawal waiver confirmation', function () { + /** @var TestCase $this */ + ['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a(); + $this->actingAs($customer); + + LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) + // Erlöschensklausel (§ 356 Abs. 4/5 BGB) als eigene Bestätigung … + ->assertSee('Ich verlange die sofortige Ausführung') + ->assertSee('Widerrufsrecht mit vollständiger Vertragserfüllung erlischt') + // … die den Bestätigen-Button mit sperrt. + ->assertSeeHtml('agb && images && contact && waiver'); +}); + +test('submitting records the waiver consent timestamp on the press release', function () { + /** @var TestCase $this */ + ['customer' => $customer, 'pr' => $pr] = makeCustomerForShowPhase8a(); + $this->actingAs($customer); + + expect($pr->withdrawal_waiver_consented_at)->toBeNull(); + + LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) + ->call('submitForReview') + ->assertHasNoErrors(); + + $pr->refresh(); + + expect($pr->status)->toBe(PressReleaseStatus::Review); + expect($pr->withdrawal_waiver_consented_at)->not->toBeNull(); +}); + +test('the index draft action opens the shared submit modal instead of a quick submit', function () { + /** @var TestCase $this */ + ['customer' => $customer] = makeCustomerForShowPhase8a(); + $this->actingAs($customer); + + LivewireVolt::test('customer.press-releases.index') + ->assertSee('Zur Prüfung') + // Gemeinsames Modal inkl. Erlöschensklausel statt wire:confirm, + // das die rechtlichen Bestätigungen umgehen würde. + ->assertSee('Ich verlange die sofortige Ausführung') + ->assertDontSeeHtml('wire:confirm'); +});