create PM v0.5
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
Kevin Adametz 2026-05-20 19:14:39 +02:00
parent 9b47296cea
commit d2ba22c0cf
25 changed files with 2155 additions and 72 deletions

View file

@ -219,6 +219,14 @@ aber:
> Die hub-flux-Roadmap ist mit Phase 6 **vollständig** abgeschlossen.
> Alle weiteren Themen sind eigene Initiativen.
**🟡 In Planung — Phase 7 (Press-Release-Form-Refactor):**
Mockup `User Neue Mitteilung presseportale.html` wird auf den
Customer-Create/Edit-Flow übertragen. Plan-Doc:
`19-PHASE-7-PRESS-RELEASE-FORM.md`.
Päckchen 7A (Migrations) → 7B (flux:editor + Sanitizer) →
7C (Customer-Create-UI) → 7D (Customer-Edit-UI) →
7E (Anhänge-Manager) → 7F (Scheduling/Embargo, optional).
1. **Manueller Dark-Mode Smoke-Test**: Im Browser User-Menü →
Erscheinung → „Dunkel" und durch die Hauptseiten klicken
(Dashboard, Listen, Detail, Security mit QR, Tokens). Erwartung:

View file

@ -0,0 +1,240 @@
# Phase 7 — Press-Release-Form-Refactor
> Großes Modul-Refactor: das zentrale „Neue Pressemitteilung"-Form
> wird auf das Mockup `User Neue Mitteilung presseportale.html` gehoben.
> Bekommt deshalb eine eigene Phase außerhalb der bisherigen
> `hub-flux`-Roadmap (Phase 06 sind dort abgeschlossen).
**Status**: 🟡 in Planung · **Aufwand**: 23 Tage · **Risiko**: mittel
(Datenmodell-Erweiterung, Editor-Format-Migration, Composer-Dependency)
---
## Ausgangslage
| Datei | Status |
|---|---|
| `resources/views/livewire/customer/press-releases/create.blade.php` | nur 1-Spalter, `<flux:textarea>`, fehlende Felder |
| `resources/views/livewire/customer/press-releases/edit.blade.php` | gleicher Stand, plus Image-Manager |
| `resources/views/livewire/admin/press-releases/create.blade.php` | Admin-Variante, dünner |
| `resources/views/livewire/admin/press-releases/edit.blade.php` | Admin-Variante |
Das Mockup verlangt einen 2-Spalter mit eigener
Settings-Sidebar (Status & Submit, Portal, Pressekontakt, Tags,
Veröffentlichung, SEO). Linke Spalte: Firma-Selector, Titel,
Untertitel, Editor, Medien, Anhänge, Boilerplate.
---
## Entscheidungen (vom User abgesegnet)
| Frage | Entscheidung |
|---|---|
| Scope | **full** — Anhänge-Tabelle + Schema-Vorbereitung für Scheduling/Embargo |
| Portal-Auswahl | **read-only** — Portal kommt immer aus der Firma; UI zeigt nur Badge |
| HTML-Sanitizer | **`mews/purifier`** — explizit approved, wird in 7B installiert |
| Pressekontakt | **genau 1 pro PM**, Single-Select aus Firmen-Kontakten; Datenmodell bleibt n:m-Pivot, Validation erzwingt `count == 1` |
| Admin-Forms | **mitziehen** in 7C+7D, gleiches Layout + Admin-only Felder |
| Default-Kontakt | **erster Firmen-Kontakt alphabetisch** (keine Schema-Änderung) |
---
## Päckchen-Aufteilung
### 7A — Migrations + Models
**Scope:**
- `press_releases.subtitle` (string 255, nullable)
- `press_releases.boilerplate_override` (text, nullable) — pro PM überschreibbare Firmen-Boilerplate
- `press_releases.scheduled_at` (timestamp, nullable) — Schema da, UI „bald"
- `press_releases.embargo_at` (timestamp, nullable) — Schema da, UI „bald"
- `companies.boilerplate` (text, nullable) — Firmenprofil-Boilerplate
- Neue Tabelle `press_release_attachments` analog `press_release_images`
(`disk`, `path`, `original_name`, `mime`, `size`, `sort_order`,
`title`, `description`, `legacy_portal`, `legacy_id`, soft-deletes,
timestamps).
- Models: `PressRelease`, `Company`, neues `PressReleaseAttachment`
+ Factory + Relationen + Casts.
- Bestehende Tests müssen grün bleiben (alle Felder nullable,
keine Verhaltensänderung).
**Akzeptanz**
- [ ] Migration up/down sauber
- [ ] `php artisan test --compact` grün (Baseline)
- [ ] `php artisan db:show press_releases / companies / press_release_attachments` zeigt neue Spalten
### 7B — Editor-Integration
**Scope:**
- `composer require mews/purifier` (explizite Approval einholen)
- Service `App\Services\PressRelease\PressReleaseHtmlSanitizer`
(Allowlist: `p,br,h2,h3,strong,em,u,ul,ol,li,blockquote,a[href|rel|target]`)
- Create/Edit-Form: `<flux:textarea>``<flux:editor>` mit
reduzierter Toolbar:
```
toolbar="heading | bold italic | bullet ordered blockquote | link | undo redo"
```
- Save-Pfad: `$this->text = $sanitizer->clean($this->text)`
- Display-Pfad (`show.blade.php`, Portal-Detail-Seiten,
`PressReleaseResource`):
- Wenn `text` HTML-Tags enthält → `{!! $clean !!}` (sanitized)
- Wenn nicht (legacy) → `{!! nl2br(e($text)) !!}`
- Helper `PressRelease::renderedText(): HtmlString`
- API: `PressReleaseResource` liefert weiterhin String (HTML),
Doku-Update `_docs/api/v1.yml` und `dev/migration 2026/07-API-MIGRATION.md`.
**Akzeptanz**
- [ ] `mews/purifier` in `composer.json`
- [ ] Alle bestehenden Plain-Text-PMs werden korrekt angezeigt
- [ ] Neue HTML-PMs werden korrekt sanitized gespeichert
- [ ] Pest-Test: `<script>`-Tag wird bei Save gestrippt
- [ ] Pest-Test: legacy plain text → `<p>`/`<br>` Rendering
### 7C — Customer-Create-Form (UI)
**Scope:**
- Page-Header wie Mockup (Crumb-Trail + Topbar mit Autosave-Status,
„Speichern", „Vorschau-bald")
- 2-Spalter `grid-cols-[1fr,360px]`
- Linke Spalte (Schreibfläche):
1. **Firma-Selector** als kompakte Inline-Pille (`flux:dropdown`)
mit „Neue Firma anlegen"-Link
2. **Titel** (`<flux:input>` mit Title-Font, Counter-Pille
`meter good/warn` 4090 Zeichen empfohlen)
3. **Untertitel** (optional, Counter-Pille 0/200)
4. **Fließtext** (`<flux:editor>` mit reduzierter Toolbar,
Counter-Pille 6003500 Z., KI-Lektorat-bald-Hint)
5. **Medien** (bestehende `press-release-images-manager`-Komponente
im neuen Tile-Style anpassen — Titelbild-Flag,
Caption/Alt-Text Pflicht-Hint)
6. **Anhänge** (neue Livewire-Komponente in 7E)
7. **Boilerplate** (Read-only-Box mit Toggle „Für diese PM
überschreiben" → öffnet `<flux:textarea>`)
- Rechte Spalte (Settings-Sidebar, sticky):
1. **Status & Absenden** (Pre-Submit-Checkliste aus
Pflichtfeldern, primärer Submit „Zur Prüfung senden")
2. **Portal** (read-only-Badge aus Firma)
3. **Pressekontakt** (Single-Select aus Firmen-Kontakten,
Pflichtfeld, Warn-Hint falls Telefon fehlt)
4. **Themen-Tags** (Chip-Eingabe, parst `keywords` als
Komma-getrennt, max 5 Tags, Vorschläge aus Firma)
5. **Veröffentlichung** (RadioGroup: „Sofort nach Freigabe"
aktiv, „Geplanter Termin" als `bald`-Badge)
6. **SEO** (Collapsed, „automatisch aus Titel" als `bald`-Hint)
7. **Phase-2-Footer-Card** wie im Mockup
- Validation:
- `title`: required, 5255
- `subtitle`: nullable, max 255
- `text`: required, min 50 (HTML-stripped)
- `company_id`: required, muss zur User-Firma gehören
- `category_id`: required
- `contact_id`: required, muss zur Firma gehören
- `keywords`: nullable, max 5 Tags
- `boilerplate_override`: nullable
- Pre-Submit-Check (Read-only-Anzeige, blockiert nicht):
- Titel vorhanden + Länge ok → ok
- Fließtext min 600 Z. → ok / sonst warn
- Firma gewählt → ok
- Mind. 1 Bild + Titelbild gesetzt → ok / sonst warn
- Mind. 1 Tag → ok / sonst warn
- Pressekontakt mit Telefon → ok / sonst warn
**Was NICHT ins 7C kommt:**
- Anhänge-Manager UI (kommt in 7E)
- Scheduling/Embargo-Logik (kommt in 7F)
- Autosave („alle paar Sek.") — erstmal nur Status-Anzeige
(„Manuell gespeichert vor X") via Livewire-Event nach `save()`;
echtes Autosave optional in 7F
**Akzeptanz**
- [ ] Bestehender Test `CustomerCompanyContextTest > customer press releases create …` grün
- [ ] Neuer Test: Create mit allen Pflichtfeldern → PR persistiert
- [ ] Neuer Test: Create ohne Pressekontakt → Validation-Fehler
- [ ] Pint + Build grün
### 7D — Customer-Edit-Form
**Scope:**
- Gleiches Layout wie 7C
- Submit-Sektion zeigt aktuellen Status (Entwurf/Abgelehnt) +
ggf. letzten Reject-Grund
- Image-Manager und Attachments-Manager weiterhin verfügbar
- Form ist nur editierbar, wenn `status in [draft, rejected]`
(bestehende Policy bleibt)
**Akzeptanz**
- [ ] Edit-Test (Update aller neuen Felder) grün
- [ ] Reject-Reason-Anzeige bleibt sichtbar
- [ ] Bestehende Tests bleiben grün
### 7E — Anhänge-Manager
**Scope:**
- Neue Volt-Komponente
`resources/views/livewire/components/press-release-attachments-manager.blade.php`
analog `press-release-images-manager.blade.php`
- Methoden: `upload`, `remove`, `moveUp`, `moveDown`
- Storage via `Storage::disk('public')` unter
`press-release-attachments/{press_release_id}/`
- Validation: PDF/DOCX/XLSX/PPTX, max. 25 MB pro Datei
- Tile-Layout wie im Mockup (PDF-Badge + Filename + Größe + Aktionen)
- Berechtigung wie beim Image-Manager (Policy `update` auf PR
+ Status `draft|rejected` für Customer)
**Akzeptanz**
- [ ] Upload-Test
- [ ] Remove-Test
- [ ] Reorder-Test
- [ ] Datei-Type-Validation grün
### 7F — Scheduling + Embargo (Schema vorhanden, UI aktivieren)
**Scope:**
- UI-Elemente aus 7C (Veröffentlichung-RadioGroup,
Embargo-Checkbox + Date-Picker) aktivieren
- `bald`-Badges entfernen
- Service-Logik in `PressReleaseService`:
- bei `submitForReview` mit `scheduled_at` → Status bleibt
`review`, beim `publish` durch Admin/Job wird `published_at`
auf `scheduled_at` gesetzt
- bei `embargo_at` → öffentliche Anzeige erst ab `embargo_at`
- Background-Job (`PublishScheduledPressReleases`) für die
Veröffentlichung um `scheduled_at`
- Test: Geplante PM wird nicht vor Termin öffentlich
- Test: Embargo-Filter im Portal-Index
**Hinweis:** 7F ist optional und kann nach 7C/D/E in eine eigene
kleine Sub-Phase ausgelagert werden, weil es einen Background-Job
einführt und der Test-Umfang separat sauber bleibt.
---
## Risiken & Mitigation
| Risiko | Mitigation |
|---|---|
| Editor-HTML bricht alte Anzeigen | Helper `renderedText()` mit Fallback auf `nl2br(e(…))` |
| Composer-Dependency `mews/purifier` ungeprüft | Vor Install explizite User-Approval; alternative `decide_later` möglich |
| Pressekontakt-Pivot mit > 1 Eintrag in DB | Migrations-Skript prüft & loggt; Validation in Save erzwingt 1 |
| Test-Suite-Regression | Jedes Päckchen einzeln + `php artisan test --compact` zwischen den Päckchen |
## Out of Scope (bewusst nicht in Phase 7)
- KI-Titel-Optimierung (Phase 8)
- KI-Lektorat (Phase 8)
- KI-Bildgenerierung (Phase 8)
- Mehrfach-Portal-Publishing (Phase 2 laut Mockup-Disclaimer)
- Versionshistorie (Phase 2 laut Mockup-Disclaimer)
- Portal-Vorschau (Phase 2)
- Echtes Autosave alle paar Sekunden (Polish, in 7F optional)
## Reihenfolge
1. **7A** Migrations + Models
2. **7B** Editor + Sanitizer
3. **7C** Customer-Create-Form
4. **7D** Customer-Edit-Form
5. **7E** Anhänge-Manager
6. **7F** Scheduling/Embargo (optional, eigene Sub-Phase)
Nach jedem Päckchen: User-Approval + Test-Run + Build + Pint + PROGRESS-Update.