Zeitzone Europe/Berlin: unnoetige UTC-Konvertierung entfernt

Beim Speichern/Veroeffentlichen waren Termine 2 Stunden versetzt: die
App-Zeitzone stand auf UTC, Termine wurden nach UTC umgerechnet, aber
published_at roh angezeigt.

- config/app.php: timezone UTC -> Europe/Berlin
- PressRelease: DISPLAY_TIMEZONE + setTimezone-Umrechnung entfernt
- Admin-/Customer-Formulare: scheduledAtUtc() -> resolveScheduledAt(),
  parst direkt in App-Zeitzone; Edit-Hydration ohne setTimezone
- ProofPdfRenderer + Boost-Anzeige: setTimezone entfernt
- Tests an verschiebungsfreies Verhalten angepasst

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-18 12:40:19 +00:00
parent 028b059975
commit 5feaa8a36a
12 changed files with 56 additions and 68 deletions

View file

@ -26,13 +26,6 @@ class PressRelease extends Model
/** @use HasFactory<PressReleaseFactory> */ /** @use HasFactory<PressReleaseFactory> */
use HasFactory, HasUniqueSlug, SoftDeletes; use HasFactory, HasUniqueSlug, SoftDeletes;
/**
* Anzeige-Zeitzone für vom Nutzer erfasste Termine (scheduled_at,
* embargo_at). In der Datenbank wird weiterhin UTC gespeichert
* (config app.timezone).
*/
public const DISPLAY_TIMEZONE = 'Europe/Berlin';
/** /**
* @return list<string> * @return list<string>
*/ */
@ -113,19 +106,20 @@ class PressRelease extends Model
} }
/** /**
* Geplanter Veröffentlichungstermin in der Anzeige-Zeitzone (Europe/Berlin). * Geplanter Veröffentlichungstermin. Die Anwendung läuft durchgängig in
* Europe/Berlin (config app.timezone), daher ist keine Umrechnung nötig.
*/ */
public function scheduledAtLocal(): ?Carbon public function scheduledAtLocal(): ?Carbon
{ {
return $this->scheduled_at?->copy()->setTimezone(self::DISPLAY_TIMEZONE); return $this->scheduled_at;
} }
/** /**
* Sperrfrist (Embargo) in der Anzeige-Zeitzone (Europe/Berlin). * Sperrfrist (Embargo) in der Anwendungs-Zeitzone (Europe/Berlin).
*/ */
public function embargoAtLocal(): ?Carbon public function embargoAtLocal(): ?Carbon
{ {
return $this->embargo_at?->copy()->setTimezone(self::DISPLAY_TIMEZONE); return $this->embargo_at;
} }
public function user(): BelongsTo public function user(): BelongsTo

View file

@ -50,7 +50,7 @@ class ProofPdfRenderer
$portalLabel = $pressRelease->portal instanceof Portal $portalLabel = $pressRelease->portal instanceof Portal
? $pressRelease->portal->label() ? $pressRelease->portal->label()
: (string) $pressRelease->portal; : (string) $pressRelease->portal;
$publishedAt = $pressRelease->published_at?->copy()->setTimezone(PressRelease::DISPLAY_TIMEZONE); $publishedAt = $pressRelease->published_at;
$dateLabel = $publishedAt?->format('d.m.Y') ?? 'n/a'; $dateLabel = $publishedAt?->format('d.m.Y') ?? 'n/a';
$timeLabel = $publishedAt?->format('H:i') ?? null; $timeLabel = $publishedAt?->format('H:i') ?? null;

View file

@ -65,7 +65,7 @@ return [
| |
*/ */
'timezone' => 'UTC', 'timezone' => 'Europe/Berlin',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -175,7 +175,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
// Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen-
// bewusst prüfen statt über die naive `after:`-Regel. // bewusst prüfen statt über die naive `after:`-Regel.
function (string $attribute, mixed $value, \Closure $fail): void { function (string $attribute, mixed $value, \Closure $fail): void {
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -219,16 +219,16 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
} }
/** /**
* Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin).
* wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. * Null, wenn kein Termin gesetzt ist.
*/ */
protected function scheduledAtUtc(): ?\Carbon\Carbon protected function resolveScheduledAt(): ?\Carbon\Carbon
{ {
if (blank($this->scheduledAt)) { if (blank($this->scheduledAt)) {
return null; return null;
} }
return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); return \Carbon\Carbon::parse($this->scheduledAt);
} }
protected function validateScheduledAtWhenReady(): void protected function validateScheduledAtWhenReady(): void
@ -239,7 +239,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
$this->resetErrorBag('scheduledAt'); $this->resetErrorBag('scheduledAt');
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -321,7 +321,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null, 'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null,
'keywords' => $this->keywords ?: null, 'keywords' => $this->keywords ?: null,
'backlink_url' => $this->backlinkUrl ?: null, 'backlink_url' => $this->backlinkUrl ?: null,
'scheduled_at' => $this->publishMode === 'scheduled' ? $this->scheduledAtUtc() : null, 'scheduled_at' => $this->publishMode === 'scheduled' ? $this->resolveScheduledAt() : null,
'embargo_at' => null, 'embargo_at' => null,
'status' => $status->value, 'status' => $status->value,
'no_export' => $this->noExport, 'no_export' => $this->noExport,

View file

@ -105,8 +105,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
$this->contactId = $pr->contacts()->withoutGlobalScopes()->first()?->id ?? $this->defaultContactIdFor((int) $pr->company_id); $this->contactId = $pr->contacts()->withoutGlobalScopes()->first()?->id ?? $this->defaultContactIdFor((int) $pr->company_id);
if ($pr->scheduled_at) { if ($pr->scheduled_at) {
// DB-Wert ist UTC; für die Eingabefelder nach Europe/Berlin wandeln. $scheduledAt = $pr->scheduled_at;
$scheduledAt = $pr->scheduled_at->copy()->setTimezone(PressRelease::DISPLAY_TIMEZONE);
$this->publishMode = 'scheduled'; $this->publishMode = 'scheduled';
$this->scheduledAt = $scheduledAt->format('Y-m-d\TH:i'); $this->scheduledAt = $scheduledAt->format('Y-m-d\TH:i');
$this->scheduledDate = $scheduledAt->format('Y-m-d'); $this->scheduledDate = $scheduledAt->format('Y-m-d');
@ -222,7 +221,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
// Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen-
// bewusst prüfen statt über die naive `after:`-Regel. // bewusst prüfen statt über die naive `after:`-Regel.
function (string $attribute, mixed $value, \Closure $fail): void { function (string $attribute, mixed $value, \Closure $fail): void {
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -266,16 +265,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
} }
/** /**
* Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin).
* wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. * Null, wenn kein Termin gesetzt ist.
*/ */
protected function scheduledAtUtc(): ?\Carbon\Carbon protected function resolveScheduledAt(): ?\Carbon\Carbon
{ {
if (blank($this->scheduledAt)) { if (blank($this->scheduledAt)) {
return null; return null;
} }
return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); return \Carbon\Carbon::parse($this->scheduledAt);
} }
protected function validateScheduledAtWhenReady(): void protected function validateScheduledAtWhenReady(): void
@ -286,7 +285,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
$this->resetErrorBag('scheduledAt'); $this->resetErrorBag('scheduledAt');
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -365,7 +364,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null, 'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null,
'keywords' => $this->keywords ?: null, 'keywords' => $this->keywords ?: null,
'backlink_url' => $this->backlinkUrl ?: null, 'backlink_url' => $this->backlinkUrl ?: null,
'scheduled_at' => $this->publishMode === 'scheduled' ? $this->scheduledAtUtc() : null, 'scheduled_at' => $this->publishMode === 'scheduled' ? $this->resolveScheduledAt() : null,
'embargo_at' => null, 'embargo_at' => null,
'no_export' => $this->noExport, 'no_export' => $this->noExport,
]); ]);

View file

@ -220,7 +220,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
// Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen-
// bewusst prüfen statt über die naive `after:`-Regel. // bewusst prüfen statt über die naive `after:`-Regel.
function (string $attribute, mixed $value, \Closure $fail): void { function (string $attribute, mixed $value, \Closure $fail): void {
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -264,16 +264,16 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
} }
/** /**
* Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin).
* wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. * Null, wenn kein Termin gesetzt ist.
*/ */
protected function scheduledAtUtc(): ?\Carbon\Carbon protected function resolveScheduledAt(): ?\Carbon\Carbon
{ {
if (blank($this->scheduledAt)) { if (blank($this->scheduledAt)) {
return null; return null;
} }
return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); return \Carbon\Carbon::parse($this->scheduledAt);
} }
protected function validateScheduledAtWhenReady(): void protected function validateScheduledAtWhenReady(): void
@ -284,7 +284,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
$this->resetErrorBag('scheduledAt'); $this->resetErrorBag('scheduledAt');
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -567,7 +567,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
'keywords' => $this->keywords ?: null, 'keywords' => $this->keywords ?: null,
'backlink_url' => $this->backlinkUrl ?: null, 'backlink_url' => $this->backlinkUrl ?: null,
'scheduled_at' => $this->publishMode === 'scheduled' 'scheduled_at' => $this->publishMode === 'scheduled'
? $this->scheduledAtUtc() ? $this->resolveScheduledAt()
: null, : null,
'embargo_at' => null, 'embargo_at' => null,
]; ];

View file

@ -108,8 +108,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
?? $this->defaultContactIdFor((int) $pr->company_id); ?? $this->defaultContactIdFor((int) $pr->company_id);
if ($pr->scheduled_at) { if ($pr->scheduled_at) {
// DB-Wert ist UTC; für die Eingabefelder nach Europe/Berlin wandeln. $scheduledAt = $pr->scheduled_at;
$scheduledAt = $pr->scheduled_at->copy()->setTimezone(PressRelease::DISPLAY_TIMEZONE);
$this->publishMode = 'scheduled'; $this->publishMode = 'scheduled';
$this->scheduledAt = $scheduledAt->format('Y-m-d\TH:i'); $this->scheduledAt = $scheduledAt->format('Y-m-d\TH:i');
$this->scheduledDate = $scheduledAt->format('Y-m-d'); $this->scheduledDate = $scheduledAt->format('Y-m-d');
@ -289,7 +288,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
// Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen-
// bewusst prüfen statt über die naive `after:`-Regel. // bewusst prüfen statt über die naive `after:`-Regel.
function (string $attribute, mixed $value, \Closure $fail): void { function (string $attribute, mixed $value, \Closure $fail): void {
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -333,16 +332,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
} }
/** /**
* Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin).
* wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. * Null, wenn kein Termin gesetzt ist.
*/ */
protected function scheduledAtUtc(): ?\Carbon\Carbon protected function resolveScheduledAt(): ?\Carbon\Carbon
{ {
if (blank($this->scheduledAt)) { if (blank($this->scheduledAt)) {
return null; return null;
} }
return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); return \Carbon\Carbon::parse($this->scheduledAt);
} }
protected function validateScheduledAtWhenReady(): void protected function validateScheduledAtWhenReady(): void
@ -353,7 +352,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
$this->resetErrorBag('scheduledAt'); $this->resetErrorBag('scheduledAt');
$scheduledAt = $this->scheduledAtUtc(); $scheduledAt = $this->resolveScheduledAt();
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
$this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
@ -435,7 +434,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
'keywords' => $this->keywords ?: null, 'keywords' => $this->keywords ?: null,
'backlink_url' => $this->backlinkUrl ?: null, 'backlink_url' => $this->backlinkUrl ?: null,
'scheduled_at' => $this->publishMode === 'scheduled' 'scheduled_at' => $this->publishMode === 'scheduled'
? $this->scheduledAtUtc() ? $this->resolveScheduledAt()
: null, : null,
'embargo_at' => null, 'embargo_at' => null,
]); ]);

View file

@ -466,7 +466,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
@if ($isBoosted) @if ($isBoosted)
<div class="rounded-[6px] border border-[color:var(--color-ok)]/30 bg-[color:var(--color-ok-soft)] px-3 py-2 text-[12.5px] text-[color:var(--color-gain-deep)]"> <div class="rounded-[6px] border border-[color:var(--color-ok)]/30 bg-[color:var(--color-ok-soft)] px-3 py-2 text-[12.5px] text-[color:var(--color-gain-deep)]">
{{ __('Aktiv bis :date', ['date' => $boostedUntil?->copy()->setTimezone(\App\Models\PressRelease::DISPLAY_TIMEZONE)->format('d.m.Y H:i')]) }} {{ __('Aktiv bis :date', ['date' => $boostedUntil?->format('d.m.Y H:i')]) }}
</div> </div>
@endif @endif

View file

@ -47,10 +47,9 @@ test('admin create form persistiert scheduled_at aus datum und uhrzeit ohne emba
$pr = PressRelease::query()->latest('id')->firstOrFail(); $pr = PressRelease::query()->latest('id')->firstOrFail();
// Eingabe 14:30 erfolgt in Europe/Berlin (CEST, +02:00) und wird als // Die App läuft in Europe/Berlin; die Eingabe 14:30 wird ohne Umrechnung
// 12:30 UTC gespeichert. // als 14:30 gespeichert.
expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 12:30:00'); expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 14:30:00');
expect($pr->scheduled_at?->copy()->setTimezone('Europe/Berlin')->format('Y-m-d H:i'))->toBe('2026-06-05 14:30');
expect($pr->embargo_at)->toBeNull(); expect($pr->embargo_at)->toBeNull();
}); });
@ -83,9 +82,8 @@ test('admin edit hydriert scheduled_at in datum und uhrzeit ohne embargo', funct
$this->actingAs($admin); $this->actingAs($admin);
$pr = PressRelease::factory()->create([ $pr = PressRelease::factory()->create([
// 12:30 UTC entspricht 14:30 in Europe/Berlin (CEST) – so wird der // Wert wird unverändert (Europe/Berlin) in den Eingabefeldern angezeigt.
// Termin in den Eingabefeldern angezeigt. 'scheduled_at' => '2026-06-05 14:30:00',
'scheduled_at' => '2026-06-05 12:30:00',
'embargo_at' => '2026-06-10 08:00:00', 'embargo_at' => '2026-06-10 08:00:00',
]); ]);
@ -121,8 +119,8 @@ test('admin edit persistiert scheduled_at aus datum und uhrzeit ohne embargo', f
$pr->refresh(); $pr->refresh();
// Eingabe 09:00 in Europe/Berlin (CEST, +02:00) wird als 07:00 UTC gespeichert. // Eingabe 09:00 (Europe/Berlin) wird ohne Umrechnung als 09:00 gespeichert.
expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-08 07:00:00'); expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-08 09:00:00');
expect($pr->embargo_at)->toBeNull(); expect($pr->embargo_at)->toBeNull();
}); });

View file

@ -91,10 +91,10 @@ test('admin show zeigt Scheduling-Termin im Review-Workflow', function () {
'scheduled_at' => '2026-06-15 10:00:00', 'scheduled_at' => '2026-06-15 10:00:00',
]); ]);
// 10:00 UTC wird in Europe/Berlin (CEST, +02:00) als 12:00 angezeigt. // Die App läuft in Europe/Berlin; 10:00 wird unverändert angezeigt.
LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id]) LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id])
->assertSee('Geplante Veröffentlichung') ->assertSee('Geplante Veröffentlichung')
->assertSee('15.06.2026 12:00'); ->assertSee('15.06.2026 10:00');
}); });
test('admin show zeigt Embargo-Info im Published-Workflow', function () { test('admin show zeigt Embargo-Info im Published-Workflow', function () {

View file

@ -52,10 +52,9 @@ test('create form persistiert scheduled_at aus datum und uhrzeit ohne embargo',
$pr = PressRelease::query()->latest('id')->firstOrFail(); $pr = PressRelease::query()->latest('id')->firstOrFail();
// Eingabe 14:30 erfolgt in Europe/Berlin (CEST, +02:00) und wird als // Die App läuft in Europe/Berlin; die Eingabe 14:30 wird ohne Umrechnung
// 12:30 UTC gespeichert. // als 14:30 gespeichert.
expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 12:30:00'); expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 14:30:00');
expect($pr->scheduled_at?->copy()->setTimezone('Europe/Berlin')->format('Y-m-d H:i'))->toBe('2026-06-05 14:30');
expect($pr->embargo_at)->toBeNull(); expect($pr->embargo_at)->toBeNull();
}); });
@ -122,9 +121,8 @@ test('edit form hydriert scheduled_at in datum und uhrzeit ohne embargo', functi
'category_id' => $category->id, 'category_id' => $category->id,
'portal' => $company->portal->value, 'portal' => $company->portal->value,
'status' => 'draft', 'status' => 'draft',
// 12:30 UTC entspricht 14:30 in Europe/Berlin (CEST) – so wird der // Wert wird unverändert (Europe/Berlin) in den Eingabefeldern angezeigt.
// Termin in den Eingabefeldern angezeigt. 'scheduled_at' => '2026-06-05 14:30:00',
'scheduled_at' => '2026-06-05 12:30:00',
'embargo_at' => '2026-06-10 08:00:00', 'embargo_at' => '2026-06-10 08:00:00',
]); ]);
$pr->contacts()->sync([$contact->id]); $pr->contacts()->sync([$contact->id]);

View file

@ -73,10 +73,10 @@ test('customer show zeigt geplante Veröffentlichung wenn gesetzt', function ()
]); ]);
$this->actingAs($customer); $this->actingAs($customer);
// 09:30 UTC wird in Europe/Berlin (CEST, +02:00) als 11:30 angezeigt. // Die App läuft in Europe/Berlin; 09:30 wird unverändert angezeigt.
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->assertSee('Geplante Veröffentlichung') ->assertSee('Geplante Veröffentlichung')
->assertSee('01.07.2026 11:30'); ->assertSee('01.07.2026 09:30');
}); });
test('customer show zeigt Sperrfrist wenn embargo_at gesetzt', function () { test('customer show zeigt Sperrfrist wenn embargo_at gesetzt', function () {
@ -87,10 +87,10 @@ test('customer show zeigt Sperrfrist wenn embargo_at gesetzt', function () {
]); ]);
$this->actingAs($customer); $this->actingAs($customer);
// 12:00 UTC wird in Europe/Berlin (CEST, +02:00) als 14:00 angezeigt. // Die App läuft in Europe/Berlin; 12:00 wird unverändert angezeigt.
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->assertSee('Sperrfrist bis') ->assertSee('Sperrfrist bis')
->assertSee('15.08.2026 14:00'); ->assertSee('15.08.2026 12:00');
}); });
test('customer show zeigt Kein-Export-Hinweis wenn no_export aktiv', function () { test('customer show zeigt Kein-Export-Hinweis wenn no_export aktiv', function () {