From 44b676116ec8848489e40fcad01b7fca55a42f68 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Wed, 17 Jun 2026 15:32:07 +0000 Subject: [PATCH] Veroeffentlichungs-Gate: proaktive Buchungs-Meldung + Schalter test-stabil - PM-Detailseite (Entwurf/Ueberarbeiten): ist kein Buchungs-Gate erfuellt, erscheint statt 'Zur Pruefung einreichen' der Hinweis 'Noch keine Buchung' mit Link zur Buchungsseite ('Buchung waehlen'). Entwurf speichern bleibt frei. - phpunit pinnt BILLING_ENFORCE_BOOKING=false, damit der Gate in Tests deterministisch bleibt (Tests, die ihn brauchen, setzen ihn per config) Co-Authored-By: Claude Opus 4.8 --- phpunit.xml | 1 + .../customer/press-releases/show.blade.php | 58 ++++++++++----- tests/Feature/PressReleaseBookingGateTest.php | 73 +++++++++++++++++++ 3 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 tests/Feature/PressReleaseBookingGateTest.php diff --git a/phpunit.xml b/phpunit.xml index 23a6bc1..04450c8 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -36,5 +36,6 @@ + diff --git a/resources/views/livewire/customer/press-releases/show.blade.php b/resources/views/livewire/customer/press-releases/show.blade.php index 1a74a34..207f4b7 100644 --- a/resources/views/livewire/customer/press-releases/show.blade.php +++ b/resources/views/livewire/customer/press-releases/show.blade.php @@ -183,6 +183,9 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends 'quotaRemaining' => $user->pressReleaseQuotaRemaining(), 'canEdit' => auth()->user()->can('update', $pr) && in_array($pr->status->value, ['draft', 'rejected']), + // Einreichen zur Prüfung ist hinter eine bezahlte Buchung gegated + // (Decision-Update §5.1, Schalter billing.enforce_booking). + 'hasActiveBooking' => $user->hasActiveBooking(), 'latestRejection' => $latestRejection, 'contacts' => $pr->contacts, 'statusLogs' => $pr->statusLogs, @@ -378,23 +381,44 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends {{ $pr->status === PressReleaseStatus::Rejected ? __('Überarbeiten') : __('Entwurf') }} -
-

- {{ $pr->status === PressReleaseStatus::Rejected - ? __('Sie können den Text bearbeiten und erneut zur Prüfung einreichen.') - : __('Reichen Sie den Entwurf ein, sobald er vollständig ist.') }} -

-
- @if ($canEdit) - - {{ __('Bearbeiten') }} - - @endif - - - {{ $pr->status === PressReleaseStatus::Rejected ? __('Erneut einreichen') : __('Zur Prüfung einreichen') }} - - +
+ @unless ($hasActiveBooking) + {{-- Veröffentlichungs-Gate: Einreichen zur Prüfung setzt eine + bezahlte Option voraus. Speichern als Entwurf bleibt frei. --}} +
+ +
+ {{ __('Noch keine Buchung.') }} + {{ __('Den Entwurf können Sie frei speichern. Zum Einreichen und Veröffentlichen benötigen Sie eine bezahlte Option (Tarif, Einzel-PM oder Extra-PM aus Guthaben).') }} +
+
+ @endunless + +
+

+ {{ $pr->status === PressReleaseStatus::Rejected + ? __('Sie können den Text bearbeiten und erneut zur Prüfung einreichen.') + : __('Reichen Sie den Entwurf ein, sobald er vollständig ist.') }} +

+
+ @if ($canEdit) + + {{ __('Bearbeiten') }} + + @endif + @if ($hasActiveBooking) + + + {{ $pr->status === PressReleaseStatus::Rejected ? __('Erneut einreichen') : __('Zur Prüfung einreichen') }} + + + @else + + {{ __('Buchung wählen') }} + + @endif +
diff --git a/tests/Feature/PressReleaseBookingGateTest.php b/tests/Feature/PressReleaseBookingGateTest.php new file mode 100644 index 0000000..60c7557 --- /dev/null +++ b/tests/Feature/PressReleaseBookingGateTest.php @@ -0,0 +1,73 @@ +seed(RolesAndPermissionsSeeder::class); +}); + +function draftForGate(): array +{ + $customer = User::factory()->create(['is_active' => true]); + $customer->assignRole('customer'); + + $company = Company::factory()->presseecho()->create(); + $customer->companies()->attach($company->id, ['role' => 'owner']); + + $pr = PressRelease::factory()->create([ + 'user_id' => $customer->id, + 'company_id' => $company->id, + 'category_id' => Category::factory()->create()->id, + 'portal' => $company->portal->value, + 'status' => 'draft', + ]); + + return compact('customer', 'pr'); +} + +test('with the gate on a draft without a booking shows the notice and links to bookings', function () { + /** @var TestCase $this */ + config()->set('billing.enforce_booking', true); + ['customer' => $customer, 'pr' => $pr] = draftForGate(); + + $this->actingAs($customer); + + LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) + ->assertSee('Noch keine Buchung') + ->assertSee('Buchung wählen') + ->assertSee(route('me.bookings.index'), false) + ->assertDontSee('Zur Prüfung einreichen'); +}); + +test('with the gate on a paid single purchase unlocks the submission action', function () { + /** @var TestCase $this */ + config()->set('billing.enforce_booking', true); + ['customer' => $customer, 'pr' => $pr] = draftForGate(); + SinglePurchase::factory()->paid()->create(['user_id' => $customer->id]); + + $this->actingAs($customer); + + LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) + ->assertDontSee('Noch keine Buchung') + ->assertSee('Zur Prüfung einreichen'); +}); + +test('with the gate off the notice never shows', function () { + /** @var TestCase $this */ + config()->set('billing.enforce_booking', false); + ['customer' => $customer, 'pr' => $pr] = draftForGate(); + + $this->actingAs($customer); + + LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) + ->assertDontSee('Noch keine Buchung') + ->assertSee('Zur Prüfung einreichen'); +});