Recht: Erloeschensklausel-Checkbox im Publikationsflow (Par. 356 BGB)
Vierte Bestaetigung im Einreichungs-Modal (sofortige Ausfuehrung, Erloeschen des Widerrufsrechts mit Vertragserfuellung); Zeitpunkt wird beim Submit als withdrawal_waiver_consented_at an der PM protokolliert. Index-Schnell-Submit (wire:confirm) durch das gemeinsame Modal ersetzt - der Pfad umging bisher alle rechtlichen Bestaetigungen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
52bc534e7d
commit
20d53988bd
6 changed files with 117 additions and 9 deletions
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Erlöschensklausel im Publikationsflow (Rechtstexte-Architektur E2):
|
||||
* Beim Einreichen bestätigt der Kunde per Checkbox, dass er die sofortige
|
||||
* Ausführung verlangt und sein Widerrufsrecht mit vollständiger
|
||||
* Vertragserfüllung erlischt (§ 356 Abs. 4/5 BGB). Der Zeitpunkt der
|
||||
* Bestätigung wird zur Nachweisbarkeit am Datensatz protokolliert.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('press_releases', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-5" x-data="{ agb: false, images: false, contact: false }">
|
||||
<div class="space-y-5" x-data="{ agb: false, images: false, contact: false, waiver: false }">
|
||||
<div>
|
||||
<flux:text class="eyebrow muted">{{ __('Veröffentlichung') }}</flux:text>
|
||||
<flux:heading size="lg">{{ __('Pressemitteilung zur Prüfung einreichen') }}</flux:heading>
|
||||
|
|
@ -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). --}}
|
||||
<flux:switch
|
||||
align="left"
|
||||
x-on:change="waiver = $event.target.checked"
|
||||
:label="__('Ich verlange die sofortige Ausführung und weiß, dass mein Widerrufsrecht mit vollständiger Vertragserfüllung erlischt.')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
|
|
@ -121,7 +128,7 @@
|
|||
</flux:modal.close>
|
||||
<flux:button variant="primary" wire:click="{{ $action }}"
|
||||
wire:loading.attr="disabled"
|
||||
x-bind:disabled="! (agb && images && contact)">
|
||||
x-bind:disabled="! (agb && images && contact && waiver)">
|
||||
{{ $confirmLabel ?? __('Veröffentlichung anfordern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -454,12 +454,19 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
<div class="flex items-center gap-1 flex-wrap">
|
||||
<span class="badge {{ $badgeClass }} dot">{{ $pr->status->label() }}</span>
|
||||
@if ($status === 'draft')
|
||||
<button type="button" class="inline-action"
|
||||
wire:click="submitForReview({{ $pr->id }})"
|
||||
wire:confirm="{{ __('Pressemitteilung zur Prüfung einreichen?') }}"
|
||||
title="{{ __('Zur redaktionellen Prüfung senden') }}">
|
||||
{{ __('Zur Prüfung →') }}
|
||||
</button>
|
||||
{{-- Öffnet das gemeinsame Einreichungs-Modal (rechtliche
|
||||
Bestätigungen inkl. Erlöschensklausel) statt eines
|
||||
Schnell-Submits, der die Bestätigungen umgehen würde. --}}
|
||||
<flux:modal.trigger :name="'confirm-submit-'.$pr->id">
|
||||
<button type="button" class="inline-action"
|
||||
title="{{ __('Zur redaktionellen Prüfung senden') }}">
|
||||
{{ __('Zur Prüfung →') }}
|
||||
</button>
|
||||
</flux:modal.trigger>
|
||||
<x-press-release-submit-modal
|
||||
:name="'confirm-submit-'.$pr->id"
|
||||
:action="'submitForReview('.$pr->id.')'"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
|
|
|||
57
tests/Feature/PressReleaseWithdrawalWaiverTest.php
Normal file
57
tests/Feature/PressReleaseWithdrawalWaiverTest.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
beforeEach(function (): void {
|
||||
/** @var TestCase $this */
|
||||
$this->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');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue