Kriterien vom Auftraggeber (12.06.2026): Quelle der Aktiv-Erkennung ist
ausschliesslich das read-only Rechnungsarchiv legacy_invoices (D-12).
Legacy-Rechnungen bleiben Archiv; neue manuelle Rechnungen entstehen im
MAN-Rechnungskreis.
- Aktiv-Regel: juengste Rechnung pro (Portal, Legacy-Vereinbarung) mit
payment_option.type=recurring und user_payment_option.status=active;
next_due_date max. --grace-months (Default 12) ueberfaellig, sonst
stale -> bleibt reines Archiv. Einmal-Kaeufe werden nie uebernommen.
- Uebernahme als grandfathered in user_payment_options:
current_period_end = next_due_date, Betraege/Intervall der letzten
Legacy-Rechnung in legacy_conditions -> der taegliche MAN-Lauf
(billing:generate-manual-invoices) fakturiert zum gewohnten
jaehrlichen Rhythmus weiter. Versteckte Katalog-Platzhalter
LEGACY-{PE|BP}-{Artikel} in payment_options.
- Replay-faehig (D-18): Re-Runs aktualisieren anhand der Legacy-IDs in
legacy_conditions statt zu duplizieren — die Kern-Migration laeuft
kurz vor dem Relaunch erneut.
- Optionen: --dry-run, --as-of, --grace-months, --no-report; JSON-Report
nach storage/app/migration/. Dry-Run gegen Test-Snapshot: 22 aktive
jaehrliche Vereinbarungen, davon 4 sofort faellig, 0 stale.
- Doku: MIGRATION-STEPS.md (Runbook-Reihenfolge nach archive-invoices),
05-DATABASE-MERGE §5.6, 12-NAECHSTE-SCHRITTE 6.6, 08-PROGRESS,
PHASE-9-Plan + Checkliste.
Tests: GrandfatherLegacySubscriptionsTest (7, inkl. End-to-End
Migration -> MAN-Rechnung mit Legacy-Betraegen). Suite: 475 passed,
4 skipped. Pint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3096 lines
145 KiB
Markdown
3096 lines
145 KiB
Markdown
# 08 – Fortschrittslog
|
||
|
||
Chronologisches Protokoll aller Migrationsschritte. Jede Session / jeder Commit hinterlässt einen Eintrag.
|
||
|
||
---
|
||
|
||
## 2026-06-12 – P6.6 Grandfathering aktiver Legacy-Abos ✅
|
||
|
||
**Phase:** P6 Daten-Migration (D-13, Kriterien vom Auftraggeber präzisiert)
|
||
**Status:** ✅ umgesetzt; Rehearsal gegen Produktiv-Snapshot bleibt P6.10.
|
||
|
||
- Neuer Command `legacy:grandfather-subscriptions` (`--dry-run`, `--as-of=`,
|
||
`--grace-months=12`, `--no-report`; JSON-Report nach
|
||
`storage/app/migration/grandfather-subscriptions-*.json`).
|
||
- Quelle ist ausschließlich das Rechnungsarchiv `legacy_invoices` (D-12):
|
||
jüngste Rechnung pro (Portal, Legacy-UPO) mit `payment_option.type =
|
||
recurring` und `user_payment_option.status = active`; `next_due_date`
|
||
max. 12 Monate überfällig, sonst stale → bleibt Archiv.
|
||
- Übernahme als `grandfathered` in `user_payment_options` mit
|
||
`current_period_end = next_due_date` und Beträgen der letzten
|
||
Legacy-Rechnung in `legacy_conditions` — der tägliche MAN-Kreis-Lauf
|
||
(`billing:generate-manual-invoices`, Phase 9D im Hauptprojekt)
|
||
fakturiert ab dann zum gewohnten jährlichen Rhythmus weiter.
|
||
- Versteckte Katalog-Platzhalter `LEGACY-{PE|BP}-{Artikel}` in
|
||
`payment_options`; Re-Runs aktualisieren statt duplizieren (D-18,
|
||
Replay kurz vor Relaunch).
|
||
- Dry-Run gegen aktuellen Test-Snapshot: 22 aktive jährliche
|
||
Vereinbarungen (49 € bis 1.190 €), davon 4 sofort fällig; 0 stale.
|
||
- Tests: `tests/Feature/Billing/GrandfatherLegacySubscriptionsTest.php`
|
||
(7 Tests, inkl. End-to-End: Migration → MAN-Rechnung mit
|
||
Legacy-Beträgen).
|
||
|
||
---
|
||
|
||
## 2026-05-04 – P6.5d Legacy-Rechnungen Vollimport + on-demand PDF ✅
|
||
|
||
**Phase:** P6 Daten-Migration + P4 Customer-Portal
|
||
**Status:** ✅ umgesetzt; finaler Vollständigkeitsnachweis erfolgt im P6.10 Staging-Rehearsal gegen aktuellen Produktiv-Snapshot.
|
||
|
||
### Was wurde gemacht
|
||
|
||
- Migration `2026_05_04_122603_add_legacy_pdf_payload_to_legacy_invoices_table` ergänzt `legacy_invoices.pdf_payload` und `pdf_generated_at`.
|
||
- `legacy:archive-invoices` importiert jetzt idempotent alle Legacy-Rechnungen mit Originalstatus, Beträgen, Datumsfeldern, Zahlart, User-Zuordnung über `legacy_import_map`, `raw_snapshot` und `pdf_payload`.
|
||
- `pdf_payload` enthält Snapshot-Daten aus `invoice`, `invoice_billing_address` und `user_payment`, damit Legacy-PDFs ohne Dateiübernahme aus DB-Daten erzeugt werden können.
|
||
- Der Command schreibt standardmäßig einen JSON-Report unter `storage/app/private/migration/legacy-invoices-*.json` mit Source-Count, Statusverteilung, Summe, Fehlern und unzugeordneten Legacy-Usern.
|
||
- Customer-Rechnungen erzeugen PDFs bei Abruf on demand über `App\Services\Billing\LegacyInvoicePdfRenderer`; vorhandene `pdf_path`-Dateien bleiben als Cache/Export weiter nutzbar. PDFs werden über `LegacyInvoicePdfController` inline ausgeliefert und im neuen Tab/Fenster geöffnet.
|
||
- Admin-Rechnungen (`admin.invoices.index`) zeigen jetzt eine echte Legacy-Archivübersicht mit Suche, Portal-/Status-/Mapping-/PDF-Filtern, Statistik-Karten, Warnhinweis für unzugeordnete Legacy-User, User-Link und PDF-Öffnen-Aktion.
|
||
- Navigation wurde getrennt: Der bestehende Archivbereich heißt im Admin und Customer-Bereich **Legacy Rechnungen**. Der Name **Rechnungen** bleibt für den späteren neuen Stripe-Rechnungskreis frei.
|
||
- Der Renderer orientiert sich an der alten Symfony-Funktion `sfpInvoicePdf` (`_show_pdf.php` / `_stern_show_pdf.php`): Kopf-/Fußdaten, Rechnungsadresse, Leistungsname, Leistungszeitraum, Rechnungsnummer, Netto/MwSt./Betrag, Zahlart, Status und Bankdaten.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php vendor/bin/pint --dirty --format agent
|
||
php artisan test --compact tests/Feature/LegacyInvoiceArchiveCommandTest.php tests/Feature/CustomerPortalTest.php
|
||
# 6 passed / 28 assertions
|
||
|
||
php artisan test --compact tests/Feature/Admin/AdminLegacyInvoiceArchiveTest.php tests/Feature/CustomerPortalTest.php
|
||
# 6 passed / 27 assertions
|
||
|
||
php artisan test --compact tests/Feature/LegacyInvoiceArchiveCommandTest.php tests/Feature/Admin/AdminLegacyInvoiceArchiveTest.php tests/Feature/CustomerPortalTest.php
|
||
# 8 passed / 52 assertions
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Tranche 4: PM-Veröffentlichungs-Block (Reject-Reason + Audit-Log + Review-Counter) ✅
|
||
|
||
**Phase:** P3 (Admin-UI) + P4 (Customer) + P5 (Domain-Services)
|
||
**Status:** ✅ umgesetzt – Basis für den vollständigen User-Login → Customer-PR-Erstellung → Admin-Freigabe-Flow.
|
||
|
||
### Was wurde gemacht
|
||
|
||
**1) Reject mit Pflicht-Begründung** (`resources/views/livewire/admin/press-releases/show.blade.php`):
|
||
- `rejectReason`-Property im Volt mit `required|string|min:5|max:2000`-Validation.
|
||
- Reject-Modal um eine `flux:textarea` mit Beispieltext erweitert.
|
||
- Begründung wird an `PressReleaseService::reject()` durchgereicht; nach Reset auf leer gesetzt und Modal geschlossen.
|
||
|
||
**2) Mail-Templates auf `me.*`-Routen umgestellt**:
|
||
- `App\Mail\PressReleaseRejected::editUrl` und `App\Mail\PressReleasePublished::showUrl` wählen jetzt rollenbasiert: Admin/Editor → `admin.press-releases.*`, Customer → `me.press-releases.*`.
|
||
- Damit landen Customer-Empfänger nicht mehr auf einer 403-Admin-Seite.
|
||
|
||
**3) Audit-Log für PR-Status-Wechsel** (neuer Tabellen- und Modell-Layer):
|
||
- Migration `2026_05_04_115118_create_press_release_status_logs_table` mit `press_release_id`, `changed_by_user_id` (NULL bei System-Aktionen), `from_status`, `to_status`, `reason`, `source` (`admin`, `customer`, `blacklist`), `created_at` + Indexen.
|
||
- Model `App\Models\PressReleaseStatusLog` mit Casts auf `PressReleaseStatus`-Enum, BelongsTo zu `PressRelease` und `User` (`changedBy`).
|
||
- `PressRelease::statusLogs()` als HasMany, sortiert `desc` nach `created_at`.
|
||
- `PressReleaseService` schreibt bei jedem Statuswechsel einen Log-Eintrag (`submitForReview`, `publish`, `reject`, `backToDraft`, `archive`, `changeStatusFromAdmin`) mit korrekter Quelle (admin/customer/blacklist) und User-ID aus `Auth::id()`.
|
||
|
||
**4) Customer-UX**:
|
||
- `customer.press-releases.show`:
|
||
- Eigene Card mit dem **prominenten Reject-Grund** (rote Callout) inklusive Datum.
|
||
- Action-Card: "Erneut einreichen" für `rejected`-Status, "Zur Prüfung einreichen" für `draft` – mit Edit-Button daneben.
|
||
- **Verlauf-Card** mit allen Status-Wechseln (Badge in passender Farbe + Datum + Reason).
|
||
|
||
**5) Admin-UX**:
|
||
- `admin.press-releases.show`: zusätzliche **Status-Verlauf-Card** mit From-/To-Status, Datum, Bearbeiter:in und Quelle (`customer`/`blacklist` als Mini-Badge sichtbar, `admin` als Default ausgeblendet).
|
||
- `admin.press-releases.index`: Quick-Actions `publish`/`reject`/`archive` gehen jetzt **konsequent durch `PressReleaseService`** – keine direkten `update()`-Calls mehr, damit Audit-Log + Mail + Cache-Invalidation immer greifen.
|
||
|
||
**6) Review-Counter in der Sidebar**:
|
||
- `AdminPerformanceCache::pressReleaseReviewCount()` als gecachte Aggregat-Methode (`StatsTtl = 60s`) + neue Cache-Key-Konstante `PressReleaseReviewCount`.
|
||
- Sidebar zeigt für Admin/Editor neben "Pressemitteilungen" einen gelben Badge mit der Anzahl wartender Reviews; Klick darauf führt direkt auf `?status=review`.
|
||
- `PressReleaseService::logStatusChange()` invalidiert beide Caches (`PressReleaseStats`, `PressReleaseReviewCount`).
|
||
|
||
### Tests
|
||
- **Neu** (`tests/Feature/PressReleaseWorkflowTest.php`, 10 Cases):
|
||
- Submit-for-Review schreibt Audit-Log mit `source=customer` und `changed_by_user_id`.
|
||
- Admin Reject mit Begründung: Status, Log, gequeuete Mail mit Reason.
|
||
- Reject ohne Begründung: Validation-Fehler, Status bleibt `review`.
|
||
- Publish-Mail nutzt `/admin/me/press-releases/...` für Customer-Empfänger.
|
||
- Reject-Mail nutzt `/admin/me/press-releases/.../edit` für Customer-Empfänger.
|
||
- Resubmit-Cycle (Draft → Review → Rejected → Review) erzeugt drei Audit-Einträge in korrekter Reihenfolge.
|
||
- Customer-Show-Page zeigt prominent den Reject-Grund + "Erneut einreichen"-Button.
|
||
- Admin-Index-Quick-Action `publish`: Status, Log, ChangedBy.
|
||
- Admin-Index-Quick-Action `reject`: Audit-Eintrag mit Default-Reason.
|
||
- `pressReleaseReviewCount()`-Helper liefert korrekte Anzahl.
|
||
|
||
### Test-Suite
|
||
- **180 Tests grün, 3 geskippt, 0 Fehler** (`php artisan test --compact`).
|
||
- `vendor/bin/pint --dirty --format agent` → fixed (kosmetik), Suite weiterhin grün.
|
||
|
||
### Plan-Updates
|
||
- `03-MIGRATION-PLAN.md`: P5.1 (`PressReleaseService`) um Audit-Log-Vermerk + Reject-Reason erweitert.
|
||
- Status-Verlauf ist jetzt die zentrale Anlaufstelle für späteres "Wer hat wann was geändert"-Reporting.
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Tranche 3: Panel-Konsolidierung ✅
|
||
|
||
**Phase:** Architekturschritt vor dem Pressemitteilungs-Veröffentlichungs-Block.
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
**Routen-Konsolidierung** (`routes/customer.php`):
|
||
- Alle vormaligen `/customer/*`-Pfade wandern unter den gemeinsamen Prefix **`/admin/me/*`** mit Routenamen-Prefix **`me.*`**.
|
||
- Mapping:
|
||
- `/customer/` → `/admin/me` (`me.dashboard`)
|
||
- `/customer/press-releases[/...]` → `/admin/me/press-releases[/...]`
|
||
- `/customer/invoices` → `/admin/me/invoices`
|
||
- `/customer/tokens` → `/admin/me/tokens`
|
||
- `/customer/profile` → `/admin/me/profile`
|
||
- `/customer/security` → `/admin/me/security`
|
||
- Alte `/customer/*`-Pfade als **301-Redirects** erhalten – Bookmarks und externe Links bleiben funktional.
|
||
- Middleware-Stack `auth + verified + EnsureUserIsCustomer + LogSlowAdminRequests` – `EnsureUserIsCustomer` lässt admin/editor/customer durch; daher haben Admins/Editor automatisch Zugriff auf "Mein Bereich" für ihre eigenen Daten.
|
||
|
||
**Rollenbasierter Login-Redirect** (`app/Http/Responses/RoleAwareLoginResponse.php`):
|
||
- Neuer `LoginResponse`-Contract (im `FortifyServiceProvider` als Singleton gebunden).
|
||
- Logik: Admin/Editor → `/dashboard`, Customer → `/admin/me`. Intended-URL wird respektiert, sofern sie nicht auf den Default-Home zeigt.
|
||
- Analoge Anpassung in `MagicLinkConsumeController` (Magic-Link-Login) und `VerifyEmailController` (Mail-Verifikation).
|
||
|
||
**Sidebar konsolidiert** (`resources/views/components/layouts/app/sidebar.blade.php`):
|
||
- "Mein Bereich" jetzt **für alle Panel-User sichtbar** (auch Admin/Editor) – sie haben dort ihre eigenen PMs/Profile/Tokens.
|
||
- Admin-spezifische Bereiche (Content, CRM, Billing, Administration, Reports) werden **nur für `canAccessAdmin()`-User** gerendert. Customer sehen ein schlankes Menü "Mein Bereich".
|
||
- Alle Customer-Items nutzen die neuen `me.*`-Routenamen.
|
||
|
||
**Volt-Components physisch unverändert**:
|
||
- `resources/views/livewire/customer/...` bleibt unter dem `customer.*`-View-Pfad bestehen (Volt-Component-Names referenzieren weiterhin die Dateipfade). Nur die HTTP-Routen-Definitionen ändern sich.
|
||
- View-interne Verlinkungen (`route('customer.*')` → `route('me.*')`) wurden in allen Volt-Files umgestellt.
|
||
|
||
### Tests
|
||
- **Neu** (`tests/Feature/PanelConsolidationTest.php`, 11 Cases):
|
||
- `/customer/*` → `/admin/me/*` als 301-Redirect (Dashboard, PMs Liste/Create/Show/Edit, Profile/Security/Tokens/Invoices)
|
||
- `me.dashboard` benötigt Login
|
||
- Customer kann `me.dashboard` aufrufen, aber nicht `dashboard` (Admin-Bereich → 403)
|
||
- Admin und Editor können beide Dashboards aufrufen
|
||
- Inaktive User → 403
|
||
- Sidebar-Sichtbarkeit: Admin sieht "Administration"; Customer nicht
|
||
- URL-Resolution-Asserts für alle `me.*`-Routen
|
||
- **Magic-Link-Login** (`tests/Feature/Auth/MagicLinkLoginTest.php`):
|
||
- Test umgebaut: Admin-Magic-Link → `/dashboard`
|
||
- Neuer Case: Customer-Magic-Link → `/admin/me`
|
||
|
||
### Test-Suite
|
||
- **170 Tests grün, 3 geskippt, 0 Fehler** (`php artisan test --compact`).
|
||
- `vendor/bin/pint --dirty --format agent` → fixed (kosmetik in `VerifyEmailController`), Suite weiterhin grün.
|
||
|
||
### Architektur-Notiz
|
||
- Mit dieser Konsolidierung ist `/admin/*` der einzige Backend-Prefix; "Mein Bereich" (`/admin/me/*`) ist eine Eigentümer-Sicht innerhalb desselben Panels. Die Volt-Component-Konvergenz (Customer-PMs ↔ Admin-PMs auf gleicher Component mit Policy-Scope) ist explizit **nicht** Teil dieser Tranche – sie wird im PM-Veröffentlichungs-Block angegangen, weil dort die echte UI-Verzahnung benötigt wird.
|
||
- Damit liegt jetzt die Basis, um den **PM-Veröffentlichungs-Block** (User-Login → Customer-PR-Erstellung → Admin-Freigabe/Bearbeitung) auf einer einheitlichen Panel-Architektur umzusetzen.
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Architektur-Entscheidung: ein gemeinsames Admin-Panel mit rollenbasierter Sichtbarkeit
|
||
|
||
Der Auftraggeber hat festgelegt: **es gibt nur ein gemeinsames Admin-Panel** unter dem Pressekonto-Backend. Admins, Editoren und Customer arbeiten im selben UI – Sichtbarkeit von Menüpunkten und Aktionen entscheidet die Rolle/Permission.
|
||
|
||
- Aktuell laufen Admin-/Editor-Funktionen unter `/admin/*` und Customer-Funktionen unter `/customer/*`. Diese Trennung wird **vor dem Pressemitteilungs-Veröffentlichungs-Block** in eine **gemeinsame Panel-Architektur** überführt.
|
||
- Sidebar wird rollenbasiert gefiltert (Admin/Editor sehen alle PMs, Customer nur seine etc.). Routen-Konsolidierung Customer → Admin-Panel.
|
||
- **Reihenfolge:**
|
||
1. Köpfe (Categories Create/Edit, Footer-Codes-CRUD) abschließen ← *erledigt*.
|
||
2. Panel-Konsolidierung: gemeinsamer Prefix, rollenbasierte Sichtbarkeit, Customer-Routen ins Admin-Panel ziehen ← *erledigt 2026-05-04*.
|
||
3. Pressemitteilungs-Veröffentlichungsblock (User-Login + Customer-PR + Admin-Freigabe/Bearbeitung) auf der konsolidierten Architektur.
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Tranche 2 (Köpfe): Categories Create/Edit + Footer-Codes-CRUD ✅
|
||
|
||
**Phase:** P3 (Admin-UI)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
**Categories Create/Edit (P3.3):**
|
||
- `CategoryTranslation::uniqueSlug(string $source, string $locale, ?int $ignoreCategoryId = null)` als zentraler Slug-Helper pro Locale.
|
||
- Volt-Components `admin.categories.create` und `admin.categories.edit`:
|
||
- DE+EN-Translations mit Live-Slug-Preview (`updatedNameDe`/`updatedNameEn`),
|
||
- Portal-Auswahl (`Both`/`presseecho`/`businessportal24`),
|
||
- Parent-Auswahl mit **Hierarchie-Loop-Schutz** (kein Self-Parent, keine Sub-Kategorie als Parent),
|
||
- Aktiv/Inaktiv-Toggle,
|
||
- Optionale Beschreibungen DE/EN,
|
||
- Lösch-Schutz: nicht löschbar, solange PMs oder Sub-Kategorien verknüpft sind.
|
||
- Index erweitert: `Kategorie anlegen`-Button und `Bearbeiten`-Pencil pro Karte; klickbare Karte für PM-Filter weiterhin per Overlay-Link.
|
||
- Routen `admin.categories.create` + `admin.categories.edit` (`routes/admin.php`).
|
||
|
||
**Footer-Codes-CRUD (P3.11):**
|
||
- Migration `2026_05_04_112430_create_footer_codes_table` legt `footer_codes` (`portal`, `language`, `title`, `content`, `is_global`, `is_active`, `priority`, Soft-Deletes, Legacy-Tracking) + Pivot `category_footer_code` an.
|
||
- Model `FooterCode` mit `belongsToMany(Category::class)`, Casts (`Portal`, Booleans).
|
||
- Factory `FooterCodeFactory` mit `global()`/`inactive()` States.
|
||
- Volt `admin.footer-codes.index` mit Suche, Portal-/Status-Filter (Aktiv/Inaktiv/Global), Karten-Statistiken (Gesamt/Aktiv/Global), `toggleActive`.
|
||
- Volt `admin.footer-codes.create` + `admin.footer-codes.edit`:
|
||
- Stammdaten (Titel, Content, Portal, Sprache, Priorität),
|
||
- Globaler Schalter (deaktiviert Kategorie-Bindung),
|
||
- Mehrfach-Auswahl von Kategorien per Checkboxen,
|
||
- Soft-Delete mit Bestätigung im Edit.
|
||
- Sidebar: `Footer-Codes`-Eintrag direkt unter `Kategorien`.
|
||
- Routen `admin.footer-codes.index/create/edit` (`routes/admin.php`).
|
||
|
||
### Tests
|
||
- `tests/Feature/Admin/AdminCategoryManagementTest.php` – 8 Cases: Render Create, Create mit beiden Translations, Pflichtfeld-Validation, Slug-Unique pro Locale bei Kollision, Edit + Slug-Rename, Parent-Loop-Schutz, Lösch-Schutz bei verknüpften PMs, erfolgreicher Delete.
|
||
- `tests/Feature/Admin/AdminFooterCodeManagementTest.php` – 9 Cases: Render Index/Create, Kategorie-gebundene Footer-Codes, Global ignoriert Kategorien, Pflichtfelder, Edit + Re-Sync der Kategorien, Toggle Global entfernt Pivot-Einträge, Soft-Delete, Toggle-Active aus Index.
|
||
|
||
### Test-Suite
|
||
- **158 Tests grün, 3 geskippt, 0 Fehler** (`php artisan test --compact`).
|
||
- `vendor/bin/pint --dirty --format agent` → passed.
|
||
|
||
### Plan-Update
|
||
- `03-MIGRATION-PLAN.md`: P1.3 erweitert um `footer_codes` + `category_footer_code`. P3.3 und P3.11 auf ✅ gesetzt.
|
||
- `dev/migration 2026/08-PROGRESS.md`: dieser Eintrag.
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Scope-Entscheidung: Newsletter vertagt
|
||
|
||
Der Auftraggeber hat entschieden, dass **NewsletterService (P5.3)** und der **Admin-Newsletter-Workflow (P3.7)** vorerst zurückgestellt werden. Beide werden nach dem Pressemitteilungs-Veröffentlichungs-Block (Login + Customer-PR + Admin-Freigabe) wiederaufgenommen. Die heutige Newsletter-Sync-UI für Legacy-Listen bleibt unverändert.
|
||
|
||
Status in `03-MIGRATION-PLAN.md` ist auf `⏸️ Vertagt 2026-05-04` gesetzt.
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Tranche 1: PR-Bilder + Blacklist + Slug-Trait + Public-Share-Link ✅
|
||
|
||
**Phase:** P3 (Admin-UI) + P4 (Customer) + P5 (Domain-Services) + P6.8 (Legacy-Bilder-Sync)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### Schritt 1 – PressRelease-Bilder durchgängig auf ImageService gestellt + Legacy-Sync (P3.2 / P3.4 / P5.5 / P6.8)
|
||
- `App\Services\Image\ImageService` erweitert um:
|
||
- `storePressReleaseImage(UploadedFile, pressReleaseId)` mit drei Varianten `thumb` (320×240), `medium` (800×600), `large` (1600×1200) per Cover-Resize (zentriert beschnitten, statt Letterbox).
|
||
- Generischer `generateVariants(...$cover = false)`-Pfad — Logos nutzen weiterhin `contained`, PR-Bilder `cover`.
|
||
- `deletePressReleaseImage()` und `generateMissingPressReleaseVariants()` für Sync- und Delete-Flows.
|
||
- Validierung mit eigenem Mime-Set (`image/jpeg|png|webp`) und 8 MB Maximum (`MAX_PRESS_RELEASE_IMAGE_BYTES`).
|
||
- `App\Models\PressReleaseImage` mit `url()` + `variantUrl(key)`-Helpers, fallback-fest auch für nicht-public Disks.
|
||
- `App\Http\Controllers\Api\V1\PressReleaseImageController` nutzt jetzt den `ImageService` (statt `Storage::disk('public')->put` + `getimagesize` direkt). API liefert `data.variants` und `data.urls.{thumb,medium,large,original}`.
|
||
- `App\Http\Resources\PressReleaseImageResource` zeigt Varianten-URLs öffentlich; `data.url` bleibt rückwärtskompatibel.
|
||
- `StorePressReleaseImageRequest::rules` von `max:5120` auf `max:8192` erhöht (synchron mit ImageService).
|
||
- Admin-Logo-Upload (`livewire/admin/companies/edit`) auf `ImageService::storeCompanyLogo()` umgestellt – inkl. Variant-Generierung, Logo-Entfernen-Button und „Logo entfernen rückgängig"-Flow.
|
||
- Wiederverwendbare Volt-Komponente `livewire/components/press-release-images-manager` für Multi-Image-Upload, Sortierung (Hoch/Runter), Preview-Flag setzen, Löschen. Wird in Admin-PR-Edit (`livewire/admin/press-releases/edit`) und Customer-PR-Edit (`livewire/customer/press-releases/edit`) per `<livewire:components.press-release-images-manager :press-release-id="$id" />` eingebunden. Authorisierung über `PressReleasePolicy::update`; Customer kann nur Draft/Rejected ändern, Admin alles außer Archived.
|
||
- Neuer Command `legacy:sync-press-release-images` (`App\Console\Commands\SyncPressReleaseImages`) analog zu `legacy:sync-company-logos`:
|
||
- `--portal=presseecho|businessportal24|all`, `--dry-run`, `--force`, `--skip-variants`, `--limit=N`.
|
||
- Kopiert nur referenzierte Legacy-Dateien aus `storage/app/public/{portal}/press_release/` nach `storage/app/public/press-releases/{prId}/images/{filename}`, generiert per `ImageService` die Varianten und aktualisiert `press_release_images` (Pfad + Varianten + Bildmaße).
|
||
- Bereits migrierte Bilder, denen Varianten fehlen, werden nachträglich versorgt.
|
||
- Berichtet Zahl der ungenutzten Quelldateien — Anhaltspunkt für Cleanup-Skripte.
|
||
- `App\Services\Import\PressReleaseImporter` setzt jetzt `legacy_portal`/`legacy_id` auch auf `press_release_images`, damit der Sync-Command stabil matchen kann.
|
||
- Tests: erweiterter `tests/Feature/Api/V1/PressReleaseImageApiTest` (Variantenprüfung, Cleanup beim Delete) + neuer `tests/Feature/SyncPressReleaseImagesTest` mit 4 Cases (Happy-Path inkl. Varianten, Dry-Run, Missing-Files → Failure, „bereits migriert + Varianten fehlen → werden generiert").
|
||
|
||
#### Schritt 2 – BlacklistService + Integration in Publish-Flow (P5.2)
|
||
- `config/blacklist.php` als initiale Wortliste (Stub – vom Auftraggeber zu erweitern oder später per Admin-UI pflegbar).
|
||
- `App\Services\PressRelease\BlacklistService`:
|
||
- case-insensitiver, ganzwörtlicher Vergleich (Sonderzeichen werden ignoriert, Mehrwort-Phrasen werden korrekt erkannt).
|
||
- Lazy-Load der Wortliste, dependency-injectable (Tests injizieren ihre eigene Liste, in Prod kommt sie aus der Config).
|
||
- `App\Services\PressRelease\PressReleaseService::submitForReview()` und `publish()` rufen jetzt `BlacklistService::findInPressRelease()` auf:
|
||
- Treffer → PM wird automatisch auf `rejected` gesetzt, Autor bekommt `PressReleaseRejected`-Mail mit Begründung („unzulässiges Wort … gefunden"), und es wird eine `BlacklistViolationException` geworfen.
|
||
- Neue `App\Services\PressRelease\BlacklistViolationException` mit dem getroffenen Wort als Property — Caller können sie gezielt abfangen ohne andere LogicExceptions mit zu erwischen.
|
||
- Caller-Schicht: Customer-PR-Detail (`livewire/customer/press-releases/show`), Customer-Liste (`.../index`), Admin-Edit (`livewire/admin/press-releases/edit`) und Admin-Detail (`livewire/admin/press-releases/show`) reagieren mit `session()->flash('error', …)` auf die Exception.
|
||
- Tests: `tests/Feature/PressReleaseBlacklistTest` mit 4 Cases (BlacklistService direkt, Submit mit verbotenem Wort, sauberer Publish, Phrase erkannt).
|
||
|
||
#### Schritt 3 – `HasUniqueSlug`-Trait (P5.6)
|
||
- Neuer Trait `App\Models\Concerns\HasUniqueSlug` mit `generateUniqueSlug(string $source, array $scope = [])`:
|
||
- Models definieren `slugScopeAttributes()` und `slugFallback()`.
|
||
- Berücksichtigt automatisch das eigene Modell, wenn es bereits gespeichert ist (verhindert „eigene Existenz blockiert sich selbst").
|
||
- Hook `applySlugConstraints(Builder)` für künftige Spezialfälle.
|
||
- `App\Models\PressRelease`: scope `['portal', 'language']`, fallback `pressemitteilung`.
|
||
- `App\Models\Company`: scope `['portal']`, fallback `company`.
|
||
- Caller umgestellt: `livewire/customer/press-releases/create`, `livewire/admin/press-releases/{create,edit}`, `livewire/admin/companies/{create,edit}` rufen jetzt `(new Model)->generateUniqueSlug(...)` bzw. `$model->generateUniqueSlug(...)` auf — die alten privaten Helfer sind raus.
|
||
- `Category` bleibt aussen vor (Slugs liegen in Translations-Tabelle, separater Mechanismus). `CompanyImporter` behält seinen eigenen Slug-Pfad (legacy-Slug bevorzugt).
|
||
- Tests: `tests/Feature/HasUniqueSlugTest` mit 4 Cases (Scope-Einhaltung, Edit hält eigenen Slug, Cross-Portal-Fallthrough, Fallback bei leerem Source).
|
||
|
||
#### Schritt 4 – Public Share-Link für PMs via Magic-Link
|
||
- `App\Services\Auth\MagicLinkGenerator::createPressReleaseShareLink(PressRelease, ?User, int $ttlDays = 7)`:
|
||
- Token + SHA-256-Hash, gleiche Sicherheitscharakteristik wie der Login-Link.
|
||
- `MagicLink.purpose = 'press_release_access'`, `payload = {press_release_id}`.
|
||
- Default 7 Tage gültig, kein Login-Effekt.
|
||
- Neue Public-Route `GET /pm-vorschau/{token}` (regex 40+ chars) → `App\Http\Controllers\PressReleasePreviewController` rendert read-only Blade-View `press-release-preview`.
|
||
- Bei ungültigem/abgelaufenem Token wird `press-release-preview-error` mit `404` bzw. `410 Gone` ausgeliefert.
|
||
- Funktioniert auch für `draft`/`review`/`rejected`/`archived`-PMs, jedoch ohne Authentifizierung — das ist der Sinn („den Kollegen den Entwurf zeigen").
|
||
- Customer-PR-Detail (`livewire/customer/press-releases/show`) hat jetzt Button „Vorschau-Link", der den Link generiert und im UI zum Kopieren anzeigt + Ablaufzeitpunkt.
|
||
- Standalone-Blade-Views nutzen reines CSS (kein Vite-Manifest, kein FluxUI), damit der Link auch unabhängig vom Build funktioniert. Dark-Mode-tauglich via `prefers-color-scheme`. `<meta name="robots" content="noindex,nofollow">` schützt vor Indexierung.
|
||
- Tests: `tests/Feature/PressReleasePreviewTest` mit 5 Cases (gültig + Inhalt sichtbar, kein Auth nötig, abgelaufen → 410, ungültiger Token → 404, force-deleted PM → 404).
|
||
|
||
#### Cleanup
|
||
- Neue `pint.json` schließt die Symfony-1.4-Legacy-Ordner (`_businessportal24.com`, `_presseecho.com`, `dev/_old`, `dev/migration 2026/_archiv`) aus, damit Pint keine PHP-5.6-Parse-Errors mehr meldet. Pint läuft jetzt grün durch (`vendor/bin/pint --dirty --format agent` → `result: passed`).
|
||
|
||
### Test-Lage
|
||
- Komplette Suite: **141 passed, 3 skipped, 0 failed** (vorher: 124 / 3). Neu hinzugekommen: 17 Tests verteilt über die 4 Schritte. Das Reporting des Slow-Admin-Logs und API-Usage bleibt unberührt.
|
||
- Pint sauber, Linter ohne neue Findings.
|
||
|
||
### Aufgeräumte Plan-Punkte (siehe `03-MIGRATION-PLAN.md`)
|
||
- ✅ P3.2 (PM-Bilder im Customer-Portal pflegen)
|
||
- ✅ P3.4 (Admin-Firmen-Logo via ImageService)
|
||
- ✅ P5.2 (BlacklistService)
|
||
- ✅ P5.5 (ImageService inkl. PR-Varianten)
|
||
- ✅ P5.6 (Slug-Trait)
|
||
- ✅ P6.8 (Legacy-PM-Bilder-Sync-Command)
|
||
- ✅ Public-Vorschau-Link (`Feature aus 06-FEATURES-SCOPE.md – Tabelle 4`)
|
||
|
||
### Offen (ohne externen Input)
|
||
- P5.3 NewsletterService neu (subscribe/confirm/unsubscribe + Admin-CSV-Export).
|
||
- P3.3 Admin Categories Create/Edit (Index existiert).
|
||
- P3.7 Admin-Newsletter-Workflow (heute nur Sync-UI).
|
||
- P3.11 Admin-CRUD für Footer-Codes.
|
||
- P5.7 Domain-Events (`PressReleasePublished` / `Rejected` / `SubmittedForReview`).
|
||
- Failure-Alerting für Queue-Worker (P9.3) + Supervisor-Setup-Doku.
|
||
|
||
### Wartet auf externen Input
|
||
- P0.5–0.7 Stripe / Produktliste / Grandfathering – blockiert die gesamte Phase 8.
|
||
- P11.2 Staging-Server, P11.4 Mailtexte für Go-Live.
|
||
|
||
---
|
||
|
||
## 2026-05-04 – Test-Suite stabilisiert + Customer-Portal abgeschlossen ✅
|
||
|
||
**Phase:** Querschnitt (Stabilisierung) + P4 (Customer-Portal) + P5.5 (ImageService)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### Paket A – Test-Suite stabilisiert
|
||
- `phpunit.xml`: `APP_URL=https://pressekonto.test` ergänzt, damit `route('login')` etc. nicht mehr auf eine fremde Domain ohne Auth-Routen zeigen.
|
||
- `tests/Feature/Auth/EmailVerificationTest.php`: Tests werden jetzt sauber per `markTestSkipped()` übersprungen, solange `Features::emailVerification()` in `config/fortify.php` deaktiviert ist. Sobald das Feature aktiviert wird, laufen die Tests automatisch wieder.
|
||
- `tests/Feature/ExampleTest.php`: testet jetzt den Health-Endpoint `/up` statt der von Vite/Theme-Layout abhängigen Startseite.
|
||
- Leerer `tests/Feature/Feature/` / `tests/Feature/Feature/Billing/`-Doppelpfad entfernt.
|
||
- Admin-Dummy-Views `admin.invoices.index`, `admin.payments.index`, `admin.coupons.index` durch ehrliche „In Vorbereitung / Vertagt"-Stubs ersetzt. Keine Fake-Daten mehr; `admin.invoices.index` zeigt zusätzlich die Anzahl bereits archivierter Legacy-Rechnungen.
|
||
|
||
#### Paket B – Customer-Portal abgeschlossen (P4.6 + P4.7 + P4.8) und ImageService-Fundament (P5.5)
|
||
- Neuer `App\Services\Image\ImageService` (GD, ohne neue Composer-Dependency):
|
||
- `storeCompanyLogo(UploadedFile, portal, companyId)` speichert Original + generiert Logo-Varianten `sq` (256×256) und `wide` (640×320) auf dem `public`-Disk unter `company-logos/{portal}/{companyId}/...`
|
||
- aspect-ratio-erhaltend mit transparentem Letterbox/Pillarbox
|
||
- `deleteCompanyLogo()` räumt Original + Varianten zuverlässig auf
|
||
- `MAX_LOGO_BYTES = 4 MB`, validierte Mime-Types JPEG/PNG/WebP/GIF
|
||
- `customer.profile`-Page (`resources/views/livewire/customer/profile.blade.php`):
|
||
- bisher read-only → jetzt voll editierbar
|
||
- User-Felder: Name, Sprache, Anrede, Vor-/Nachname, Titel, Telefon, Adresse, Land, Backlink-URL, USt-ID, Show-Stats, Disable-Footer-Code
|
||
- Companies-Liste zeigt Rolle und Eigentümer-Badge; nur Eigentümer/Responsible können bearbeiten
|
||
- Inline Company-Editor mit Logo-Upload, Logo-Vorschau und Logo-Entfernen-Aktion (befüllt `companies.logo_path` + `companies.logo_variants`)
|
||
- Neue `customer.security`-Page (`resources/views/livewire/customer/security.blade.php`):
|
||
- Passwort ändern (mit `current_password`-Rule)
|
||
- E-Mail-Adresse ändern (setzt `email_verified_at = null` und löst, falls aktiviert, Fortify-Verifikationsmail aus)
|
||
- Zwei-Faktor-Authentifizierung aktivieren/deaktivieren, QR-Code anzeigen, Recovery-Codes regenerieren
|
||
- Customer-Sidebar um „Profil" und „Sicherheit" erweitert.
|
||
- `routes/customer.php`: neue Route `customer.security`.
|
||
|
||
#### P4.8 – Policies eingeführt
|
||
- Neue Policies: `PressReleasePolicy`, `CompanyPolicy`, `ContactPolicy`, `LegacyInvoicePolicy`.
|
||
- Customer-Komponenten ersetzt verstreute `where('user_id', auth()->id())`-Filter durch `$this->authorize(...)`-Aufrufe:
|
||
- `customer.press-releases.show` (view + submitForReview)
|
||
- `customer.press-releases.edit` (update)
|
||
- `customer.invoices` (downloadPdf)
|
||
- `PressReleasePolicy` kennt eigene Ability `submitForReview` und Admin-Ability `publish` (verknüpft mit `press-releases:publish` Permission).
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --dirty --format agent
|
||
# unsere Dateien automatisch formatiert; Errors nur in _businessportal24.com (Legacy-Referenz, nicht unser Scope)
|
||
|
||
php artisan test --compact
|
||
# 124 passed, 3 skipped (vorher 108 passed, 12 failed)
|
||
|
||
php artisan test --compact tests/Feature/CustomerProfileSecurityTest.php
|
||
# 7 passed (Profil-Editor, Logo-Upload + Variants, Company-Auth-Negativtest, Security-Render, Passwort-Change, PressRelease- und LegacyInvoice-Policy-Negativtests)
|
||
```
|
||
|
||
### Plan-Status nach dieser Session
|
||
- P3.4 Logo-Upload mit Varianten: Customer-Pfad ✅; Admin-Pfad nutzt jetzt dieselbe Service-Schicht und kann nachgezogen werden.
|
||
- P4.6 Customer-Profile/Company-Pflege ✅
|
||
- P4.7 Customer-Security (Passwort/2FA/E-Mail) ✅
|
||
- P4.8 Policies ✅ für PressRelease, Company, Contact, LegacyInvoice
|
||
- P5.5 `ImageService`-Fundament ✅ (Logos); PressRelease-Bilder können auf gleicher Service-Klasse aufsetzen.
|
||
|
||
### Nächster Schritt
|
||
|
||
- P8 (Stripe/Billing) bleibt der größte Releaseblocker; benötigt P0.5–0.7 (Stripe-Test-Account, Produktliste, Grandfathering-Kriterien) vom Auftraggeber.
|
||
- Ohne externe Inputs gut machbar:
|
||
- PressRelease-Bilder-Upload im Customer-/Admin-Portal über `ImageService` ergänzen (mehrere Bilder, Alt-Texte, Sortierung)
|
||
- `legacy:sync-press-release-images` als Pendant zu `legacy:sync-company-logos`
|
||
- Admin-Logo-Upload/-Edit auf `ImageService` umstellen
|
||
- `BlacklistService` (P5.2) und neuer `NewsletterService` (P5.3)
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Users: Datenstandsfilter für Pressemitteilungen ✅
|
||
|
||
**Phase:** P3 (Admin-UI / User-Verwaltung)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
- Datenstandsfilter in der User-Übersicht um Pressemitteilungen erweitert:
|
||
- `Mit Pressemitteilungen`
|
||
- `Ohne Pressemitteilungen`
|
||
- `Mit veröffentlichten PMs`
|
||
- `Ohne veröffentlichte PMs`
|
||
- Veröffentlichte PMs werden statusbasiert über `PressReleaseStatus::Published` gefiltert.
|
||
- Regressionstest ergänzt, der User mit veröffentlichter PM korrekt ein- und ausschließt.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent resources/views/livewire/admin/users.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 22 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Users: Filter für PM-Freigabe ✅
|
||
|
||
**Phase:** P3 (Admin-UI / User-Verwaltung)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
- Neue Berechtigung `press-releases:publish` ergänzt.
|
||
- Seeder weist `press-releases:publish` den Rollen `admin` und `editor` zu.
|
||
- User-Übersicht hat jetzt einen Berechtigungsfilter:
|
||
- `Kann PMs freigeben`
|
||
- Der Filter berücksichtigt:
|
||
- Super-Admins
|
||
- Rollen mit `press-releases:publish`
|
||
- direkt zugewiesene User-Permissions mit `press-releases:publish`
|
||
- In der Rollen-Spalte erscheint bei passenden Usern ein Badge `PM-Freigabe`.
|
||
- Rollen-/Permissiondaten werden eager geladen, damit die Badge-Ausgabe keine N+1-Queries erzeugt.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent database/seeders/RolesAndPermissionsSeeder.php resources/views/livewire/admin/users.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 22 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Users: Veröffentlichte PMs in Übersicht ✅
|
||
|
||
**Phase:** P3 (Admin-UI / User-Verwaltung)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
- User-Übersicht zeigt jetzt pro Benutzer die Anzahl veröffentlichter Pressemitteilungen.
|
||
- Umsetzung performant über `withCount()` mit Alias `published_press_releases_count` und Status-Constraint auf `published`.
|
||
- Entwürfe/andere Status werden in dieser Badge bewusst nicht mitgezählt.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent resources/views/livewire/admin/users.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 22 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Users: Detail-Modal erweitert ✅
|
||
|
||
**Phase:** P3 (Admin-UI / User-Verwaltung)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
- User-Detail-Modal deutlich übersichtlicher strukturiert:
|
||
- Header mit Account-, Portal-, Registrierungstyp- und Legacy-Informationen.
|
||
- Kennzahlenkarten für Firmen, Kontakte, Pressemitteilungen, API-Tokens und Zahloptionen.
|
||
- getrennte Karten für Account/Zugriff, Legacy-Profil, Rechnungsadresse und Datenqualität.
|
||
- Firmenbereich fachlich erweitert nach `User → Firmen → Kontakte / Pressemitteilungen`:
|
||
- Firmen zeigen Rolle, Portal, Kontaktanzahl, PM-Anzahl und Status.
|
||
- Kontakte werden je Firma direkt angezeigt.
|
||
- aktuelle Pressemitteilungen werden je Firma mit Status, Datum, Portal und Hits angezeigt.
|
||
- Daten werden weiterhin eager geladen, damit im Modal keine Blade-seitigen Zusatzqueries entstehen.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent resources/views/livewire/admin/users.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 22 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Users: Workflow-Filter und Bearbeitungsübersicht ✅
|
||
|
||
**Phase:** P3 (Admin-UI / User-Verwaltung)
|
||
**Status:** ✅ umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
- User-Übersicht erweitert um Rollenfilter und Datenstandsfilter:
|
||
- ohne/mit Firma
|
||
- ohne/mit Legacy-Profil
|
||
- ohne Kontakte
|
||
- ohne Rechnungsadresse
|
||
- Tabelle verdichtet:
|
||
- Benutzername, E-Mail und ID in einer Benutzer-Spalte.
|
||
- Zuordnungsstatus zeigt Firmen- und Kontaktanzahl.
|
||
- Datenqualität zeigt Profil- und Rechnungsstatus direkt als Badges.
|
||
- Filter können direkt zurückgesetzt werden; aktive Filter werden sichtbar markiert.
|
||
- User-Edit-Seite ergänzt um eine Bearbeitungsübersicht mit Sprungpunkten zu Account, Profil, Firmen, Kontakten und Rechnung.
|
||
- Edit-Flow zeigt Status-Badges für Aktivität, Portal, Profil, Firmen/Kontakte und Rechnungsadresse.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent resources/views/livewire/admin/users.blade.php resources/views/livewire/admin/users/edit.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 22 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-29 – Bugfix: Leere Profil-Datumsfelder bleiben leer ✅
|
||
|
||
**Phase:** P3 (Admin-UI / User-Edit)
|
||
**Status:** ✅ behoben
|
||
|
||
### Problem
|
||
|
||
In `admin.users.edit` konnten leere Legacy-Profil-Datumsfelder (`birthdate`, `validation_date`, `contract_date`) im Date-Input als aktuelles Tagesdatum erscheinen.
|
||
|
||
### Was wurde gemacht
|
||
|
||
- Profil-Datumsfelder werden im Volt-State jetzt als leere Strings statt `null` initialisiert.
|
||
- Beim Laden eines Profils werden fehlende Datumswerte explizit als `''` gesetzt.
|
||
- Beim Speichern werden leere Strings weiterhin als `null` in `profiles` persistiert.
|
||
- Die drei Datumsfelder nutzen `flux:date-picker type="input"` mit `clearable`, weil `flux:input type="date"` leere Werte sichtbar mit dem Tagesdatum vorbelegen konnte.
|
||
- Regressionstest ergänzt: leere Profil-Datumsfelder bleiben leer und werden nicht als heutiges Datum gespeichert.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent resources/views/livewire/admin/users/edit.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 21 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-29 – Admin Users: Firmenstatus + Detail-Modal ✅
|
||
|
||
**Phase:** P3 (Admin-UI / UX)
|
||
**Status:** ✅ User-Übersicht verbessert
|
||
|
||
### Was wurde gemacht
|
||
|
||
- `admin.users` zeigt jetzt in der Übersicht, ob ein Benutzer mit Firmen verknüpft ist:
|
||
- grünes Badge mit Anzahl bei vorhandenen Firmen
|
||
- neutrales Badge „Keine Firma" ohne Verknüpfung
|
||
- Der bisherige Auge-Link navigiert nicht mehr auf die Detailseite, sondern öffnet ein Flux-Modal direkt in der Übersicht.
|
||
- Das Modal enthält die wichtigsten Informationen aus der bisherigen Detailseite:
|
||
- Basisdaten, Status, Portal, Typ
|
||
- Legacy-Profil-Kurzübersicht
|
||
- Rechnungsadresse
|
||
- verknüpfte Firmen inkl. Rolle und Kontakte
|
||
- direkter Link zur Bearbeiten-Seite
|
||
- Die bestehende `admin.users.show`-Route bleibt weiterhin vorhanden, wird aus der Übersicht aber nicht mehr als Standard-Flow genutzt.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent resources/views/livewire/admin/users.blade.php tests/Feature/Admin/UserManagementTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 20 passed
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-29 – Legacy-User-Profile nachgezogen ✅
|
||
|
||
**Phase:** P6/P3 (Datenmigration + Admin-UI)
|
||
**Status:** ✅ `sf_guard_user_profile` wird jetzt als eigenes Profil importiert und im Admin editierbar
|
||
|
||
### Was wurde gemacht
|
||
|
||
- `UserImporter` erweitert:
|
||
- liest die relevanten Felder aus `sf_guard_user_profile`
|
||
- schreibt/aktualisiert die Daten in `profiles`
|
||
- mappt Legacy-Anreden (`salutation_id`) auf `mr`/`mrs`/`none`
|
||
- mappt die wichtigsten Legacy-Länder-IDs auf die neuen Country-Codes (`DE`, `AT`, `CH`, ...)
|
||
- bereinigt Textfelder und behandelt leere/ungültige Datumswerte sicher
|
||
- `Profile` um Factory-Unterstützung ergänzt
|
||
- `ProfileFactory` ergänzt
|
||
- `admin.users.edit` erweitert:
|
||
- lädt `user.profile`
|
||
- zeigt die Legacy-Profilfelder als eigenen Abschnitt an
|
||
- speichert Änderungen per `profile()->updateOrCreate()`
|
||
- löscht ein Profil nur, wenn alle Profilfelder bewusst geleert wurden
|
||
- Tests ergänzt:
|
||
- Import-Test für `sf_guard_user_profile` → `profiles`
|
||
- Admin-User-Edit-Test für Laden/Speichern der Profilfelder
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
vendor/bin/pint --format agent app/Models/Profile.php database/factories/ProfileFactory.php app/Services/Import/UserImporter.php resources/views/livewire/admin/users/edit.blade.php tests/Feature/Admin/UserManagementTest.php tests/Feature/LegacyUserProfileImportTest.php
|
||
# passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/LegacyUserProfileImportTest.php
|
||
# 1 passed
|
||
|
||
ulimit -n 65535 && php artisan test --compact tests/Feature/Admin/UserManagementTest.php
|
||
# 19 passed
|
||
```
|
||
|
||
### Hinweis
|
||
|
||
Kombinierte Testläufe ohne ausreichend hohes File-Descriptor-Limit sind in dieser Umgebung mit `Too many open files` in PHPUnit/Pest abgestürzt. Die betroffenen Tests laufen in frischen Prozessen mit `ulimit -n 65535` sauber durch.
|
||
|
||
---
|
||
|
||
## 2026-04-29 – Dokumentationsabgleich gegen echten Code-Stand ✅
|
||
|
||
**Phase:** Querschnitt / Projektsteuerung
|
||
**Status:** ✅ zentrale Migrationsdokumente synchronisiert
|
||
|
||
### Was wurde gemacht
|
||
|
||
Der aktuelle Code-Stand wurde gegen die Migrationsdokumentation abgeglichen. Bestätigt wurden insbesondere:
|
||
- P2.5/P2.7: Admin-Schutz, Portal-Scoping, Portal-Switcher und Go-Live-Mail-Command vorhanden
|
||
- P3: Dashboard, PressRelease-CRUD/-Workflow, Users/Roles, Companies/Contacts und Categories-Index weitgehend produktiv angebunden
|
||
- P4: Customer-Portal-Grundumfang inkl. PMs, Legacy-Rechnungsarchiv und API-Token Self-Service vorhanden
|
||
- P5.1: `PressReleaseService` und Published/Rejected-Mailables vorhanden
|
||
- P6/P7: Import-Kern, Verify, API v1, Usage-Logging, Rate-Limit und API-Kundenreport vorhanden
|
||
|
||
Aktualisierte Dokumente:
|
||
- `03-MIGRATION-PLAN.md` – Status-Spalten auf echten Stand nachgezogen
|
||
- `README.md` – Schnellüberblick aktualisiert und Dokumente 09/10 ergänzt
|
||
- `04-DATA-MODEL.md` – `contact_user` dokumentiert
|
||
- `07-API-MIGRATION.md` – Legacy-Cutover einheitlich auf `410 Gone` korrigiert
|
||
- `09-REVIEW-QUALITAET.md` – als historisches Review markiert
|
||
- `MIGRATION-STEPS.md` – aktuelles Kurz-Runbook konsolidiert
|
||
|
||
### Weiter offen
|
||
|
||
- P8 Stripe/Billing/Invoicing bleibt der größte technische Blocker
|
||
- Grandfathering-Kriterien, Medienübernahme und Staging-Rehearsal sind vor Go-Live weiter offen
|
||
- API-Kundenfreigabe benötigt fachliche Klärung für die meisten Legacy-API-User
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7 Abschlussprüfung API v1 ✅
|
||
|
||
**Phase:** P7 (API v1)
|
||
**Status:** ✅ P7 technisch abgeschlossen
|
||
|
||
### Entscheidung Newsletter-Unsubscribe
|
||
|
||
`newsletter/unsubscribe` wird nicht als Legacy-API-Endpoint umgesetzt:
|
||
- es gibt keinen relevanten Legacy-API-Client für Newsletter-Unsubscribe
|
||
- Newsletter wird später im neuen Frontend neu implementiert
|
||
- die Anbindung erfolgt dann über einen sauberen Newsletter-Dienst
|
||
|
||
### P7-Status
|
||
|
||
Abgeschlossen:
|
||
- Sanctum API v1 mit Kern-Endpoints
|
||
- Legacy-Key-Cutover (`410 Gone`)
|
||
- API-Dokumentation (`docs/api/v1.yml`)
|
||
- Legacy-API-Kundenreport
|
||
- Token-Freigabe nur für aktive/zahlende bzw. freigegebene User
|
||
- Runtime-Sperre inaktiver User
|
||
- API-Usage-Logging
|
||
- API-Usage-Reporting
|
||
- Rate-Limiting pro Token
|
||
- Image-Minimal-Endpoints
|
||
|
||
Nicht mehr Teil von P7:
|
||
- `newsletter/unsubscribe`
|
||
- spätere Newsletter-Neuentwicklung / externer Newsletter-Dienst
|
||
- Bildderivate/Thumbnails/erweiterte Medienverwaltung
|
||
- Admin-Reporting-UI für API-Nutzung
|
||
|
||
### Ergebnis
|
||
|
||
Nach aktuellem Scope ist in P7 nichts Technisches mehr offen.
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7 Images: Minimal-API-Endpoints ✅
|
||
|
||
**Phase:** P7 (API v1)
|
||
**Status:** ✅ Minimaler Image-Scope umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
Neue Endpoints:
|
||
|
||
```bash
|
||
GET /api/v1/press-releases/{pressRelease}/images
|
||
POST /api/v1/press-releases/{pressRelease}/images
|
||
DELETE /api/v1/press-release-images/{pressReleaseImage}
|
||
```
|
||
|
||
Regeln:
|
||
- List benötigt `press-releases:read`
|
||
- Upload/Delete benötigt `press-release-images:write`
|
||
- nur Bilder eigener Pressemitteilungen
|
||
- Upload/Delete nur bei `draft` oder `rejected`
|
||
- Upload nur JPEG/PNG/WebP bis maximal 5 MB
|
||
- Dateien werden auf dem `public`-Disk unter `press-releases/{id}/images` gespeichert
|
||
- `PressReleaseResource` liefert geladene Bilder inkl. URL mit aus
|
||
|
||
OpenAPI-Doku (`docs/api/v1.yml`) und API-Migrationsplan wurden angepasst.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/Api/V1/PressReleaseImageApiTest.php tests/Feature/Api/V1/PressReleaseApiTest.php tests/Feature/Api/V1/CustomerDataApiTest.php tests/Feature/ApiAccessSecurityAndLoggingTest.php tests/Feature/ApiUsageReporterTest.php tests/Feature/ApiDocumentationTest.php
|
||
# 18 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- P7-Abschlussentscheidung dokumentieren
|
||
- Optional später: Bildderivate/Thumbnails, Sortierung, Alt-Texte und Admin-UI erweitern
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7.7 API-Rate-Limiting pro Token ✅
|
||
|
||
**Phase:** P7 (API v1 / Security & Reporting)
|
||
**Status:** ✅ Rate-Limit pro Sanctum-/Bearer-Token ergänzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
Neue Middleware `EnsureApiTokenRateLimit`:
|
||
- hängt in der `/api/v1`-Gruppe nach `auth:sanctum` und nach der Aktivitätsprüfung
|
||
- limitiert auf 60 Requests pro Minute
|
||
- segmentiert bevorzugt nach Sanctum-/Bearer-Token-ID
|
||
- nutzt als Fallback einen SHA-256-Fingerprint des Bearer-Tokens, danach User-ID/IP
|
||
- liefert bei Überschreitung HTTP 429 inkl. `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`
|
||
|
||
Damit kann ein einzelner API-Client begrenzt werden, ohne automatisch alle anderen Tokens desselben Users zu blockieren.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/ApiAccessSecurityAndLoggingTest.php tests/Feature/ApiUsageReporterTest.php tests/Feature/CustomerPortalTest.php tests/Feature/Api/V1/CustomerDataApiTest.php tests/Feature/Api/V1/PressReleaseApiTest.php
|
||
# 16 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- P7-Abschlussentscheidung dokumentieren
|
||
- Optional: Admin-Reporting-UI auf Basis von `ApiUsageReporter` ergänzen
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7 Reporting: API-Usage-Report ✅
|
||
|
||
**Phase:** P7 (API v1 / Security & Reporting)
|
||
**Status:** ✅ Auswertbarer API-Nutzungsreport ergänzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
Neue zentrale Auswertung `ApiUsageReporter`:
|
||
- aggregiert `api_usage_logs`
|
||
- unterstützt Filter nach Zeitraum, User-ID und HTTP-Statuscode
|
||
- liefert Summary, Top-Pfade, Statuscodes, Top-User, Top-Tokens und letzte Requests
|
||
|
||
Neuer Command:
|
||
|
||
```bash
|
||
php artisan api:usage-report
|
||
php artisan api:usage-report --from=2026-04-01 --to=2026-04-30
|
||
php artisan api:usage-report --user=123
|
||
php artisan api:usage-report --status=403
|
||
php artisan api:usage-report --no-report
|
||
```
|
||
|
||
Der JSON-Report wird standardmäßig unter `storage/app/private/migration/api-usage-*.json` abgelegt.
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/ApiUsageReporterTest.php
|
||
# 2 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- P7-Abschlussentscheidung dokumentieren
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7 Security: API-Key-Freischaltung + Usage-Logging ✅
|
||
|
||
**Phase:** P7 (API v1 / Security & Reporting)
|
||
**Status:** ✅ API-Zugang abgesichert und Nutzung persistent protokolliert
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### API-Token-Erstellung abgesichert
|
||
|
||
Neue zentrale Prüfung `ApiAccessEligibilityService`:
|
||
- User muss aktiv sein (`is_active = true`)
|
||
- API-Token-Erstellung ist nur möglich bei:
|
||
- aktivem neuen Zahlungsstatus (`user_payment_options.status = active` im gültigen Zeitraum)
|
||
- aktivem Bestandsschutz (`status = grandfathered`, `grandfathered_until >= heute`)
|
||
- Übergangsweise: letzte importierte Legacy-Rechnung ist `paid`
|
||
|
||
Die Customer-Seite `customer.tokens` zeigt bei fehlender Freigabe einen Hinweis und erstellt serverseitig keinen Token.
|
||
|
||
#### Bestehende Tokens inaktiver User blockiert
|
||
|
||
Neue Middleware `EnsureApiUserIsActive`:
|
||
- hängt an der `/api/v1`-Gruppe nach `auth:sanctum`
|
||
- blockiert Requests inaktiver User mit HTTP 403
|
||
- schützt auch dann, wenn ein User bereits früher einen Sanctum-Token erzeugt hatte
|
||
|
||
#### API-Usage-Logging
|
||
|
||
Neue Tabelle `api_usage_logs` + Middleware `LogApiUsage`:
|
||
- loggt alle `/api/*` Requests inkl. 410-Legacy-Key-Ablehnungen und 403-Inactive-User-Blocks
|
||
- speichert User-ID, Sanctum-Token-ID, Methode, Pfad, Route, Statuscode, IP, User-Agent, Dauer, Zeitpunkt
|
||
- speichert keine Bearer-Tokens, keine Legacy-`api_key`s und keine Payloads
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/ApiAccessSecurityAndLoggingTest.php tests/Feature/CustomerPortalTest.php tests/Feature/Api/V1/CustomerDataApiTest.php tests/Feature/Api/V1/PressReleaseApiTest.php
|
||
# 13 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- Migration auf Ziel-/Staging-DB ausführen (`api_usage_logs`)
|
||
- Optional: Admin-Reporting-UI für API-Nutzungsstatistiken ergänzen
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7.10 Legacy-API-Kundenreport vorbereitet ✅
|
||
|
||
**Phase:** P7 (API v1 / Kundenkommunikation)
|
||
**Status:** ✅ Datenbankbasierter Report-Command + Dokumentation angelegt
|
||
|
||
### Was wurde gemacht
|
||
|
||
Nach Legacy-Codeprüfung wurde entschieden, die API-Kundenliste nicht aus App-Logs abzuleiten:
|
||
- Symfony-Prod-Logging ist deaktiviert (`logging_enabled: false`, `sfNoLogger`)
|
||
- API-Zugriffe werden nicht applikationsseitig persistiert
|
||
- `X-ApiKey` wird in Standard-Webserver-Logs typischerweise nicht gespeichert
|
||
|
||
Neuer Command:
|
||
|
||
```bash
|
||
php artisan api:legacy-customers-report
|
||
```
|
||
|
||
Der Command ermittelt aus der migrierten DB:
|
||
- Kandidaten: `users.registration_type = apiuser` oder Rolle `api-only`
|
||
- automatisch freigabefähig: User aktiv + letzte Legacy-Rechnung `paid`
|
||
- `needs_review`: aktiver API-Kandidat ohne archivierte Legacy-Rechnung
|
||
- `blocked`: inaktiv oder letzte Rechnung nicht bezahlt
|
||
|
||
Der JSON-Report wird nach `storage/app/private/migration/legacy-api-customers-*.json` geschrieben.
|
||
|
||
Separate Dokumentation:
|
||
- `dev/migration 2026/10-LEGACY-API-KUNDEN.md`
|
||
|
||
### Aktuelle Datenlage
|
||
|
||
- 195 importierte `apiuser`
|
||
- 1 API-User mit Legacy-Rechnung
|
||
- 1 automatisch freigabefähiger API-User nach aktueller Regel
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/LegacyApiCustomerReporterTest.php
|
||
# 2 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- Report gegen die migrierte DB ausführen und Ergebnis fachlich prüfen
|
||
- Falls API-User nicht selbst Rechnungsempfänger waren: Legacy-Zuordnung API-User → zahlender Vertrags-/Billing-User nachliefern oder zusätzlich importieren
|
||
- Kommunikationsmail für `eligible`-Kunden vorbereiten
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7.1 Legacy-API-Log-Auswertung vorbereitet ✅
|
||
|
||
**Phase:** P7 (API v1)
|
||
**Status:** ✅ Analyse-Tooling implementiert; echte Produktiv-Logs noch externer Input
|
||
|
||
### Was wurde gemacht
|
||
|
||
Neuer Artisan-Command:
|
||
|
||
```bash
|
||
php artisan api:analyze-legacy-access-logs /path/to/access.log "/path/to/access.log.*" --top=20
|
||
```
|
||
|
||
- parst Apache/Nginx-Access-Logs nach Legacy-API-Routen wie `pressrelease/list`, `pressrelease/create`, `company/list`, `newsletter/subscribe`
|
||
- erkennt `api_key`-Query-Parameter
|
||
- schreibt keine Klartext-API-Keys in Reports, sondern nur SHA-256-Fingerprints
|
||
- aggregiert:
|
||
- Top-Endpunkte
|
||
- Top-Client-IPs
|
||
- Statuscodes
|
||
- Anzahl eindeutiger API-Key-Fingerprints
|
||
- maskierte Beispielrequests
|
||
- JSON-Report wird standardmäßig nach `storage/app/private/migration/legacy-api-access-*.json` geschrieben
|
||
- `--no-report` für reine Konsolenausgabe
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/LegacyApiAccessLogAnalyzerTest.php tests/Feature/ApiDocumentationTest.php
|
||
# 3 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- Produktiv-Access-Logs nur auswerten, falls später noch verfügbar; sie sind kein P7-Blocker mehr
|
||
- API-Kundenliste wird sicher aus migrierten Daten und Legacy-Codeprüfung abgeleitet
|
||
|
||
---
|
||
|
||
## 2026-04-29 – P7.8 API-Dokumentation ergänzt ✅
|
||
|
||
**Phase:** P7 (API v1)
|
||
**Status:** ✅ OpenAPI-Dokumentation verfügbar
|
||
|
||
### Was wurde gemacht
|
||
|
||
- OpenAPI-Spezifikation unter `docs/api/v1.yml` angelegt
|
||
- Web-Route `GET /docs/api/v1` ergänzt, damit der `docs_url` aus dem Legacy-Key-410-Handler erreichbar ist
|
||
- Customer-Token-Seite um Link zur API-Dokumentation erweitert
|
||
- Dokumentiert sind die aktuell implementierten API-v1-Endpunkte:
|
||
- `GET|POST /api/v1/press-releases`
|
||
- `GET|PATCH|DELETE /api/v1/press-releases/{pressRelease}`
|
||
- `GET /api/v1/companies`
|
||
- `GET /api/v1/companies/{company}`
|
||
- `GET /api/v1/categories`
|
||
- `POST /api/v1/newsletter/subscribe`
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/ApiDocumentationTest.php tests/Feature/CustomerPortalTest.php tests/Feature/Api/V1/PressReleaseApiTest.php tests/Feature/Api/V1/CustomerDataApiTest.php
|
||
# 10 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- Erledigt in Folgeeinträgen: Legacy-API-Kundenreport, Rate-Limiting, Image-Minimal-Endpoints und P7-Abschlussentscheidung
|
||
|
||
---
|
||
|
||
## 2026-04-28 – P7 gestartet: API v1 mit Sanctum + Legacy-Key-Cutover ✅
|
||
|
||
**Phase:** P7 (API v1)
|
||
**Status:** ✅ Kern-Endpunkte implementiert
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### API-Routing aktiviert
|
||
|
||
- `routes/api.php` wird jetzt in `bootstrap/app.php` als API-Routendatei geladen
|
||
- Sanctum Ability-Middleware-Aliase (`abilities`, `ability`) registriert
|
||
- BasicAuth überspringt `/api/*`, da API-Zugriffe über Sanctum Bearer Tokens geschützt sind
|
||
|
||
#### Legacy-Key-Cutover (P7.6)
|
||
|
||
Neue Middleware `RejectLegacyApiKeys`:
|
||
- erkennt `api_key` Query-Parameter und `X-Api-Key` Header
|
||
- antwortet vor Sanctum-Auth mit HTTP **410 Gone**
|
||
- liefert `migration_url` zum Customer-Token-Bereich und `docs_url`
|
||
|
||
#### API v1 Endpunkte
|
||
|
||
Neue `/api/v1`-Endpunkte:
|
||
- `GET /api/v1/press-releases`
|
||
- `POST /api/v1/press-releases`
|
||
- `GET /api/v1/press-releases/{pressRelease}`
|
||
- `PATCH /api/v1/press-releases/{pressRelease}`
|
||
- `DELETE /api/v1/press-releases/{pressRelease}`
|
||
- `GET /api/v1/companies`
|
||
- `GET /api/v1/companies/{company}`
|
||
- `GET /api/v1/categories`
|
||
- `POST /api/v1/newsletter/subscribe`
|
||
|
||
Alle schreibenden/lesenden Aktionen prüfen Sanctum-Abilities (`press-releases:*`, `companies:read`, `newsletter:subscribe`) und begrenzen Customer-Daten serverseitig auf eigene Firmen/Pressemitteilungen.
|
||
|
||
#### Resources & Validation
|
||
|
||
- `PressReleaseResource`, `CompanyResource`, `CategoryResource`
|
||
- `StorePressReleaseRequest`, `UpdatePressReleaseRequest`, `SubscribeNewsletterRequest`
|
||
- Slug-Erzeugung für API-Pressemitteilungen inkl. Kollisionsschutz pro Portal/Sprache
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/Api/V1/PressReleaseApiTest.php tests/Feature/Api/V1/CustomerDataApiTest.php tests/Feature/CustomerPortalTest.php
|
||
# 9 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- P7.8: API-Dokumentation/OpenAPI ergänzen
|
||
- P7.1/P7.10: Legacy-Access-Logs auswerten und API-Kunden-Kommunikation vorbereiten
|
||
- Optional: Rate-Limiting pro Token ergänzen (P7.7)
|
||
|
||
---
|
||
|
||
## 2026-04-28 – P4 erweitert: API-Tokens + Legacy-Rechnungen im Customer-Portal ✅
|
||
|
||
**Phase:** P4 (Customer-Portal)
|
||
**Status:** ✅ Self-Service für API-Tokens und Archivrechnungen umgesetzt
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### API-Token Self-Service (P4.5)
|
||
|
||
Neue Customer-Seite: `customer.tokens`
|
||
- Sanctum Personal Access Tokens erstellen
|
||
- Scope-Auswahl pro Token:
|
||
- `press-releases:read`
|
||
- `press-releases:write`
|
||
- `press-release-images:write`
|
||
- `companies:read`
|
||
- `newsletter:subscribe`
|
||
- Plaintext-Token wird nur direkt nach Erstellung angezeigt
|
||
- Tokens können vom eigenen User widerrufen werden
|
||
|
||
#### Legacy-Rechnungsarchiv im Kundenkonto (P4.4)
|
||
|
||
Neue Customer-Seite: `customer.invoices`
|
||
- zeigt ausschließlich `legacy_invoices` des eingeloggten Users
|
||
- Statistik-Kacheln: Anzahl, Archivsumme, bezahlte Rechnungen, PDFs
|
||
- Suche nach Rechnungsnummer + Statusfilter
|
||
- PDF-Download vorbereitet: Wenn `pdf_path` vorhanden und Datei existiert, wird über Storage ausgeliefert; sonst erscheint ein Hinweis
|
||
|
||
#### Navigation & Rollen
|
||
|
||
- Customer-Sidebar um „Rechnungen" und „API-Tokens" erweitert
|
||
- `routes/customer.php` um `customer.invoices.index` und `customer.tokens.index` ergänzt
|
||
- `RolesAndPermissionsSeeder` um `newsletter:subscribe` ergänzt, damit API-Scope und Permission-Set zusammenpassen
|
||
|
||
### Verifikation
|
||
|
||
```bash
|
||
php artisan test --compact tests/Feature/CustomerPortalTest.php
|
||
# 2 passed
|
||
```
|
||
|
||
### Nächster Schritt
|
||
|
||
- P7 starten: API v1 mit Sanctum-Abilities, API-Resources und 410-Gone-Handler für alte `api_key`s
|
||
- Danach: Customer-Token-Seite mit Link zur API-Doku verbinden
|
||
|
||
---
|
||
|
||
## 2026-04-28 – Phase 6 abgeschlossen: Vollständiger Datenmigrations-Import ✅
|
||
|
||
**Phase:** P6 (Datenmigration)
|
||
**Status:** ✅ Beide Portale vollständig migriert
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### PressReleaseImporter – Timestamps und Slug-Fix
|
||
|
||
- `withoutTimestamps` + `created_at`/`updated_at` aus Legacy-DB (wie Companies/Contacts)
|
||
- **Slug-Kollisions-Fix bei `--force`**: Beim Update-Lauf wird der bestehende Slug des Records beibehalten (`existingPr?->slug`). Neu: `uniqueSlug()` nimmt optionalen `excludeId`-Parameter.
|
||
- `legacy:fix-timestamps` um `press-releases` Entität erweitert
|
||
|
||
#### Businessportal24 – Companies + Contacts importiert
|
||
|
||
```bash
|
||
php artisan legacy:import --source=businessportal24 --step=companies --force
|
||
# 66.525 Firmen | 0 Fehler | 488s
|
||
|
||
php artisan legacy:import --source=businessportal24 --step=contacts --force
|
||
# 8.597 Kontakte | 0 Fehler | 40s
|
||
```
|
||
|
||
#### Pressemitteilungen – beide Portale
|
||
|
||
```bash
|
||
php artisan legacy:import --source=presseecho --step=press-releases --force
|
||
# 74.384 importiert | 5.649 übersprungen (kein User in DB) | 0 Fehler
|
||
|
||
php artisan legacy:import --source=businessportal24 --step=press-releases --force
|
||
# 100.304 aktualisiert | 1 Fehler (Slug-Kollision → behoben)
|
||
```
|
||
|
||
#### legacy:archive-invoices (P6.5)
|
||
|
||
Neuer Command: archiviert alle Legacy-Rechnungen in `legacy_invoices` (read-only Archiv).
|
||
- `safeDateOrNull()` behandelt Legacy-Datum `0000-00-00` → null (MySQL-Strict-Mode Fix)
|
||
- 299 Rechnungen Presseecho + 565 Businessportal24 = **864 Rechnungen** archiviert
|
||
|
||
> Nachtrag 2026-05-04: Dieser Stand ist nur als erster Archivlauf/Vorlauf zu werten. Für den Go-Live wurde P6.5d ergänzt: vollständiger Legacy-Rechnungsimport inkl. Status, User-Zuordnung, PDF-Renderdaten und Import-Report; PDF-Erzeugung bleibt für Legacy-Rechnungen DB-basiert/on demand.
|
||
|
||
```bash
|
||
php artisan legacy:archive-invoices --dry-run # prüfen
|
||
php artisan legacy:archive-invoices # beide Portale
|
||
```
|
||
|
||
#### Post-Import-Bereinigung
|
||
|
||
```bash
|
||
php artisan legacy:import --step=link-associations --force
|
||
# 16.968 contact_user Einträge (beide Portale)
|
||
|
||
php artisan legacy:fix-timestamps # alle Entitäten, beide Portale
|
||
# 174.688 PMs + alle anderen → historische Timestamps korrekt
|
||
```
|
||
|
||
### Finaler Datenstand
|
||
|
||
| Entität | Presseecho | Businessportal24 | Gesamt |
|
||
|---|---|---|---|
|
||
| Users | 19.372 | 46.778 | **66.150** |
|
||
| Companies | 41.310 | 66.525 | **107.836** |
|
||
| Contacts | 8.367 | 8.597 | **16.964** |
|
||
| PressReleases | 74.384 | 100.304 | **174.688** |
|
||
| LegacyInvoices | 299 | 565 | **864** |
|
||
| contact_user Links | – | – | **16.968** |
|
||
| legacy_import_map | – | – | **369.150** |
|
||
|
||
### Timestamps-Verifikation
|
||
- PMs mit `created_at = heute`: **0** ✅
|
||
- Älteste PM: `2006-04-21` (Telekom WM-Sponsor) ✅
|
||
- Ältester User: `2010-07-23` ✅
|
||
|
||
### Konsistenz-Verifikation (P6.9)
|
||
|
||
Neuer Command: `php artisan legacy:verify`
|
||
- prüft Legacy-/Ziel-Counts, Legacy-Rechnungsarchiv, verwaiste `legacy_import_map`-Ziele, Ziel-Foreign-Keys, portalübergreifende User-Merges und Grandfathering-Zusammenfassung
|
||
- schreibt JSON-Reports nach `storage/app/private/migration/verify-*.json` (optional `--no-report`)
|
||
- unterstützt `--portal=presseecho|businessportal24|all` und `--skip-legacy` für lokale/testbare Ziel-DB-Checks
|
||
|
||
```bash
|
||
php artisan legacy:verify --no-report
|
||
# 0 Fehler | 0 Warnungen
|
||
```
|
||
|
||
### Vollständige Import-Reihenfolge (Go-Live Runbook)
|
||
|
||
```bash
|
||
php artisan legacy:import --source=presseecho --step=categories --force
|
||
php artisan legacy:import --source=all --step=users --force
|
||
php artisan legacy:import --source=presseecho --step=companies --force
|
||
php artisan legacy:import --source=businessportal24 --step=companies --force
|
||
php artisan legacy:import --source=presseecho --step=contacts --force
|
||
php artisan legacy:import --source=businessportal24 --step=contacts --force
|
||
php artisan legacy:import --source=presseecho --step=press-releases --force
|
||
php artisan legacy:import --source=businessportal24 --step=press-releases --force
|
||
php artisan legacy:import --step=link-associations --force
|
||
php artisan legacy:archive-invoices
|
||
php artisan legacy:fix-timestamps
|
||
php artisan legacy:verify
|
||
```
|
||
|
||
### Nächster Schritt
|
||
- P0.5–0.7: Stripe-Zugangsdaten + Produktliste vom Auftraggeber (blockiert P8)
|
||
- P7: API v1 (Endpoints, Sanctum Token-Abilities, 410 Gone Handler)
|
||
- P10: Security-Review + Test-Coverage
|
||
- P11: Staging-Deployment + Rehearsal-Lauf + Go-Live-Mailing
|
||
|
||
---
|
||
|
||
## 2026-04-27 – Import-Bugfixes + Timestamps + User-Kontakt-Verknüpfung
|
||
|
||
**Phase:** P6 (Datenmigration – Qualitätssicherung)
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### Import-Fehler behoben: phone/fax zu kurz (VARCHAR 40 → 255)
|
||
|
||
Beim echten Companies-Import (Presseecho) schlugen 20 Datensätze fehl, weil Legacy-Telefonnummern Beschreibungstext enthielten (bis 92 Zeichen, z.B. „0700 27862537 (0700 ARTMaker, zum Festnetztarif de)").
|
||
|
||
- **Migration** `2026_04_27_115212_expand_phone_fax_columns`: `phone` + `fax` in `companies` und `contacts` von VARCHAR(40) auf **VARCHAR(255)** erweitert.
|
||
- **`CompanyImporter` + `ContactImporter`**: neue `cleanText()`-Methode dekodiert HTML-Entities (` ` etc.) und kürzt auf die Feldlänge als letzten Fallback.
|
||
- Re-Import mit `--force`: 41.290 Firmen aktualisiert, 20 neu importiert → **0 Fehler**.
|
||
|
||
#### Timestamps aus Legacy-DBs übernehmen
|
||
|
||
**Problem:** Alle 119k importierten Datensätze hatten `created_at = heute`, da die Importers ursprünglich kein `withoutTimestamps` nutzten.
|
||
|
||
**Fixes:**
|
||
- `UserImporter`: `User::withoutTimestamps()` + `created_at`/`updated_at` aus `sf_guard_user` übernehmen.
|
||
- `CompanyImporter` (bereits vorhanden) + `ContactImporter` (bereits vorhanden): beide nutzen jetzt korrekt `withoutTimestamps`.
|
||
- Neuer Command **`legacy:fix-timestamps`** für bereits importierte Daten: nutzt Cross-DB-JOINs auf dem gleichen MySQL-Server – korrigiert 119.287 Datensätze in **2,6 Sekunden**.
|
||
|
||
```bash
|
||
php artisan legacy:fix-timestamps --dry-run # erst prüfen
|
||
php artisan legacy:fix-timestamps # alle Entitäten, beide Portale
|
||
php artisan legacy:fix-timestamps --entity=companies --portal=presseecho
|
||
```
|
||
|
||
**Ergebnis nach Fix:** Ältester User `2010-07-23`, älteste Firma `2011-07-05`, ältester Kontakt `2011-07-07`. ✅
|
||
|
||
#### Neuer Import-Schritt: `link-associations` (User↔Kontakt direkt)
|
||
|
||
**Hintergrund:** Im Legacy-System gab es keine direkte User→Kontakt-Tabelle. Die Zugehörigkeit war indirekt: User → company_user → company → contact. Im neuen System gibt es `contact_user`, das jetzt befüllt wird.
|
||
|
||
- Neuer Service **`UserAssociationLinker`**: ableitet User↔Kontakt-Links aus `company_user + contacts` via JOIN.
|
||
- `ImportLegacyData`-Command erweitert um `--step=link-associations` (letzter Schritt in `--step=all`).
|
||
- Idempotent via `insertOrIgnore` – manuell im Admin angelegte Links bleiben erhalten.
|
||
- **8.367 contact_user-Einträge** in 0,2 Sekunden erstellt.
|
||
|
||
```bash
|
||
php artisan legacy:import --step=link-associations --force
|
||
```
|
||
|
||
#### ImportContext erweitert
|
||
|
||
`ImportContext` unterstützt jetzt `portal = 'all'` (kein Legacy-DB-Zugriff nötig) für portalübergreifende Schritte wie `link-associations`.
|
||
|
||
### Vollständige Import-Reihenfolge (aktualisiert)
|
||
|
||
```bash
|
||
php artisan legacy:import --source=presseecho --step=categories --force
|
||
php artisan legacy:import --source=all --step=users --force
|
||
php artisan legacy:import --source=presseecho --step=companies --force
|
||
php artisan legacy:import --source=businessportal24 --step=companies --force
|
||
php artisan legacy:import --source=presseecho --step=contacts --force
|
||
php artisan legacy:import --source=businessportal24 --step=contacts --force
|
||
php artisan legacy:import --source=presseecho --step=press-releases --force
|
||
php artisan legacy:import --source=businessportal24 --step=press-releases --force
|
||
php artisan legacy:import --step=link-associations --force
|
||
php artisan legacy:fix-timestamps
|
||
```
|
||
|
||
### Verifikation
|
||
- Companies Presseecho: 41.310 importiert ✅
|
||
- Users (beide Portale): ~69.650 importiert ✅
|
||
- Timestamps: alle historisch korrekt ✅
|
||
- contact_user: 8.367 Verknüpfungen ✅
|
||
|
||
---
|
||
|
||
## 2026-04-27 – Admin-UI: Tabellen sortierbar + 100 pro Seite + Spalten-Fixes
|
||
|
||
**Phase:** P3 (Admin-UI Qualität)
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### Alle Admin-Tabellen: Pagination auf 100, sortierbare Spalten
|
||
|
||
Alle 6 Tabellen-Views auf **100 Einträge pro Seite** erhöht (vorher: 12–20). Flux-Pro `sortable` / `sorted` / `direction` Props mit `wire:click="sort('column')"` in alle Köpfe integriert.
|
||
|
||
| View | Sortierbare Spalten | Standard |
|
||
|---|---|---|
|
||
| Users | Vorname, E-Mail, Portal, Status, Letzter Login, Hinzugefügt | Hinzugefügt ↓ |
|
||
| Companies | Name, E-Mail, Status, PMs, Kontakte, Hinzugefügt | Name ↑ |
|
||
| Contacts | Name, E-Mail, Firma, Hinzugefügt | Nachname ↑ |
|
||
| PMs (Admin) | Titel, Status, Portal, Hits, Erstellt | Erstellt ↓ |
|
||
| Kategorien | Standard (ID), PMs | ID ↑ |
|
||
| PMs (Kunde) | Titel, Status, Erstellt | Erstellt ↓ |
|
||
|
||
#### Spalten-Fixes
|
||
|
||
**Companies Index:**
|
||
- „Website"-Spalte entfernt
|
||
- „Hinzugefügt" (created_at) hinzugefügt
|
||
|
||
**Contacts Index:**
|
||
- „Position"-Spalte entfernt
|
||
- „Hinzugefügt" (created_at) war bereits vorhanden und korrekt
|
||
|
||
**Users Index:**
|
||
- Bug behoben: Aktions-Icons erschienen in der „Erstellt"-Spalte (Datenzelle fehlte)
|
||
- Name aufgeteilt: „Vorname" + „Nachname" als separate Spalten (aus `name` gesplittet, kein separates DB-Feld)
|
||
- „Typ"-Spalte entfernt, Colspan im Empty-State korrigiert
|
||
|
||
**Company Show – Kontakte-Tab:**
|
||
- Input + Select + „Zuordnen"-Button ersetzt durch Flux-Combobox mit Live-Suche (ab 1 Zeichen, max. 50, Auswahl löst sofort `attachExistingContact()` aus)
|
||
|
||
#### Contacts Index – Company-Filter als Combobox
|
||
|
||
**Problem:** `Company::query()->orderBy('name')->get()` lud nach dem Import alle 82.820 Firmen in den Livewire-State → Livewire-Fehler.
|
||
|
||
**Fix:** Company-Filter jetzt als Flux-Combobox mit Backend-Suche. Ohne Eingabe: kein Load. Mit Eingabe: max. 50 Firmen live. Livewire-State enthält maximal 50 statt 82.820 Firmen.
|
||
|
||
---
|
||
|
||
## 2026-04-27 – Phase 6 gestartet: Legacy-Import-Infrastruktur + Dry-Run ✅
|
||
|
||
**Phase:** P6 (Datenmigration)
|
||
**Status:** ✅ Infrastruktur + Dry-Run abgeschlossen – bereit für echten Import
|
||
|
||
### Was wurde gemacht
|
||
|
||
**Voraussetzung erfüllt (P0.3):**
|
||
Legacy-DB-Connections funktionieren:
|
||
- `mysql_presseecho` → presseecho (43 Tabellen)
|
||
- `mysql_businessportal` → businessportal (42 Tabellen)
|
||
|
||
**Neue Dateien – Import-Infrastruktur (`app/Services/Import/`):**
|
||
- `ImportContext` – hält Portal, Connection, dry-run/force-Flags
|
||
- `ImportResult` – zählt importiert/übersprungen/aktualisiert/Fehler
|
||
- `UserImporter` – sf_guard_user + sf_guard_user_profile + Rollen (inkl. Gruppen-Mapping)
|
||
- `CompanyImporter` – company + company_user + responsible_company_user → Pivot mit Rollen
|
||
- `ContactImporter` – contact → contacts (Salutation-Mapping)
|
||
- `CategoryImporter` – category + category_translation → categories + translations (DE/EN)
|
||
- `PressReleaseImporter` – press_release + press_release_image + press_release_contact
|
||
|
||
**Master-Command:** `app/Console/Commands/ImportLegacyData.php`
|
||
- Befehl: `php artisan legacy:import`
|
||
- Optionen: `--source=presseecho|businessportal24|all`, `--step=categories|users|companies|contacts|press-releases|all`, `--dry-run`, `--force`
|
||
- Idempotent via `legacy_import_map` (skip bereits importierter Datensätze)
|
||
- Chunk-basiert (500 Records pro DB-Query) – speichereffizient
|
||
|
||
**Status-Mapping Legacy → Neu:**
|
||
| Legacy | Neu |
|
||
|---|---|
|
||
| `new` / `edited` | `draft` |
|
||
| `prepublished` | `review` |
|
||
| `published` | `published` |
|
||
| `rejected` | `rejected` |
|
||
|
||
**Gruppen-Mapping Legacy → Rollen:**
|
||
| Gruppe | Rolle |
|
||
|---|---|
|
||
| admin (1) | admin |
|
||
| editor (2) | editor |
|
||
| dataFetcher (3) | api-only |
|
||
| unlimitedPressReleasesPayment (4) | customer |
|
||
| keine Gruppe | customer |
|
||
|
||
### Dry-Run-Ergebnisse (0 Fehler)
|
||
|
||
| Schritt | presseecho | businessportal24 | Zeit |
|
||
|---|---|---|---|
|
||
| categories | 14 | – (identisch) | <1s |
|
||
| users | 19.372 (292 skip) | ~47k | 2s |
|
||
| companies | 41.310 | 66.525 | 4–7s |
|
||
| contacts | 8.366 | 8.597 | <1s |
|
||
| press-releases | 80.033 | 100.305 | 31–45s |
|
||
| **Gesamt** | **~149k** | **~223k** | **37 / 58s** |
|
||
|
||
### Nächster Schritt
|
||
- **Echten Import starten:** `php artisan legacy:import --source=presseecho --step=categories --force` (Kategorien zuerst)
|
||
- Danach: `--step=users` → `--step=companies` → `--step=contacts` → `--step=press-releases`
|
||
- Ggf. Legacy-Rechnungen archivieren (`legacy:archive-invoices` – noch nicht implementiert, P6.5)
|
||
|
||
---
|
||
|
||
## 2026-04-27 – P2.7 + P4 + P5.1 + P9.2: GoLive-Mailing, Customer-Portal, Services, Scheduler
|
||
|
||
**Phase:** P2.7 · P4 · P5.1 · P9.2
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
|
||
**P2.7 – GoLive-Mailing**
|
||
- `App\Mail\GoLivePasswordReset` + Template `emails/auth/go-live-password-reset.blade.php`
|
||
- `App\Console\Commands\SendGoLiveMails` (`auth:send-go-live-mails`):
|
||
- `--dry-run` → zählt nur, schickt nichts
|
||
- `--portal=presseecho` → filtert nach Portal
|
||
- `--limit=10` → für Tests
|
||
- `--force` → überspringt Bestätigungsabfrage
|
||
- Erzeugt Fortify-Passwort-Reset-Token pro User, sendet personalisierten Link
|
||
|
||
**P5.1 – PressReleaseService + Benachrichtigungen**
|
||
- `App\Services\PressRelease\PressReleaseService` mit Methoden:
|
||
- `submitForReview()`, `publish()`, `reject($reason)`, `backToDraft()`, `archive()`
|
||
- `assertStatus()` wirft `LogicException` bei ungültigem Übergang
|
||
- `notifyAuthor()` sendet automatisch Mail nach `publish`/`reject` via Queue
|
||
- `App\Mail\PressReleasePublished` + Template
|
||
- `App\Mail\PressReleaseRejected` + Template (mit optionalem Ablehnungsgrund)
|
||
- Admin-Views `press-releases/edit.blade.php` + `show.blade.php` nutzen jetzt den Service
|
||
- Service als Singleton in `AppServiceProvider` gebunden
|
||
|
||
**P9.2 – Scheduler-Commands**
|
||
- `App\Console\Commands\PurgeMagicLinks` (`magic-links:purge --days=30`): löscht verbrauchte/abgelaufene Links
|
||
- `App\Console\Commands\PurgeExpiredPressReleaseDrafts` (`press-releases:purge-drafts --days=180 --dry-run`): archiviert Zombie-Entwürfe
|
||
- `routes/console.php` mit Laravel-Scheduler-Einträgen:
|
||
- täglich 03:00: Magic-Links bereinigen
|
||
- wöchentlich sonntags 04:00: PM-Entwürfe archivieren
|
||
|
||
**P4 – Customer-Portal Grundgerüst**
|
||
- `App\Http\Middleware\EnsureUserIsCustomer` → prüft `canAccessCustomer()`
|
||
- `routes/customer.php`: Route-Gruppe `customer.*` mit Middleware, in `domains.php` eingebunden
|
||
- Neue Volt-Komponenten unter `resources/views/livewire/customer/`:
|
||
- `dashboard.blade.php`: Statistik-Widgets, letzte eigene PMs, Firmenübersicht
|
||
- `press-releases/index.blade.php`: gefilterte Liste eigener PMs, inline „Zur Prüfung einreichen"
|
||
- `press-releases/create.blade.php`: Firma aus eigenem Bestand (kein Combobox-Vollzugriff), Sicherheitscheck
|
||
- `press-releases/edit.blade.php`: nur für `draft`/`rejected` PMs erlaubt (403 sonst)
|
||
- `press-releases/show.blade.php`: mit `authorizeAccess()` (nur eigene PMs), Prüfungs-Workflow
|
||
- `profile.blade.php`: Konto-Info + zugeordnete Firmen
|
||
- Sidebar: Customer-Only-Navigation für User ohne Admin-Rechte
|
||
|
||
### Verifikation
|
||
- `php artisan auth:send-go-live-mails --dry-run` → ✅ zählt korrekt
|
||
- `php artisan magic-links:purge` + `press-releases:purge-drafts --dry-run` → ✅
|
||
- `php artisan test --compact` → 33 relevante Tests grün ✅
|
||
|
||
### Nächster Schritt
|
||
- P0.3: `.env` mit Legacy-DB-Connections (blockiert P6)
|
||
- P6: Datenmigration sobald Legacy-Credentials vorliegen
|
||
- P4 erweitern: API-Token Self-Service, Rechnungen-Archiv-Tab
|
||
|
||
---
|
||
|
||
## 2026-04-27 – P3 abgeschlossen: PressRelease-CRUD + Categories + Dashboard
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Restliche Dummy-Data-Views auf echte DB-Queries umgestellt + UX-Verbesserungen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
|
||
**Kategorien-Index** (`admin.categories.index`)
|
||
- Echte Eloquent-Queries mit `withCount('pressReleases')` und DE/EN-Translations
|
||
- Suchfilter via `whereHas('translations', ...)`
|
||
- Statistik-Widgets (Gesamt, Mit PMs, PMs gesamt) mit echten Counts
|
||
|
||
**Pressemitteilungen – komplett neu (alle 4 Views)**
|
||
- **Index** (`admin.press-releases.index`): Tabelle mit echten Daten, Filter nach Status/Portal/Sprache, Live-Schnellaktionen (Veröffentlichen, Ablehnen, Archivieren) direkt aus der Liste
|
||
- **Create** (`admin.press-releases.create`): 2-Spalten-Layout (Content | Sidebar), Firma per Flux-Combobox (Live-Suche ab 1 Zeichen), Kategorie-Select, Slug-Generierung, „Als Entwurf" / „Zur Prüfung einreichen"
|
||
- **Edit** (`admin.press-releases.edit`): lädt echte DB-Daten, vollständiger Status-Workflow (Entwurf → Prüfung → Veröffentlicht → Archiviert), Slug-Update nur bei Titel-/Portal-/Sprachänderung
|
||
- **Show** (`admin.press-releases.show`): Read-only Detail mit Status-Badge, Prüfungs-Aktionsleiste (Veröffentlichen / Ablehnen direkt auf der Seite), Bilder-Liste
|
||
|
||
**Status-Workflow:**
|
||
- Draft → Review (`submitForReview`) → Published (`publish`) / Rejected (`reject`)
|
||
- Published → Archived (`archive`)
|
||
- Review/Rejected → Draft (`backToDraft`)
|
||
|
||
**Dashboard** (`resources/views/admin/dashboard.blade.php`)
|
||
- 5 Statistik-Kacheln (PMs, Firmen, Kontakte, Benutzer, Newsletter) – alle klickbar, echte DB-Counts
|
||
- Letzte 8 PMs mit Status-Badge
|
||
- Prüfwarteschlange (PMs im Status `review`) mit Direktlinks
|
||
|
||
**UX-Verbesserungen (Contacts + Users)**
|
||
- `contacts/create.blade.php` + `contacts/edit.blade.php`: Firma-Auswahl auf Flux-Combobox mit Live-Backend-Suche umgestellt (kein All-Load mehr)
|
||
- `users/edit.blade.php`: Firmen- und Kontakt-Lookup auf Flux-Combobox umgestellt, Auswahl triggert sofortige Zuordnung via `updatedSelectedLookup*`-Hooks (kein separater „Zuordnen"-Button)
|
||
- Suche startet ab 1 Zeichen, max. 50 Treffer, `withoutGlobalScopes()` für portalübergreifenden Admin-Zugriff
|
||
|
||
### Verifikation
|
||
- `php artisan test --compact` → 33 relevante Tests grün ✅
|
||
- Alle Views auf echte Eloquent-Models umgestellt
|
||
|
||
### Nächster Schritt
|
||
- P2.7: `GoLivePasswordReset`-Mailable + Command `auth:send-go-live-mails`
|
||
- P0.3: `.env` mit Legacy-DB-Connections konfigurieren (Voraussetzung für P6)
|
||
- Phase 6 (Datenmigration) – kritischer Pfad zum Go-Live
|
||
|
||
---
|
||
|
||
## 2026-04-27 – Qualitätsreview + kritische Fixes (Sicherheit, Architektur, Testqualität)
|
||
|
||
**Phase:** Querschnitt P2/P3 (Qualitätssicherung + Sicherheit)
|
||
**Aufgaben:** Umsetzung aller kritischen und wichtigen Punkte aus `09-REVIEW-QUALITAET.md`.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
|
||
#### 🔴 Kritische Sicherheitslücken behoben
|
||
|
||
**1. Admin-Middleware `EnsureUserIsAdmin`**
|
||
- Neu: `app/Http/Middleware/EnsureUserIsAdmin.php`
|
||
- Prüft `$request->user()?->canAccessAdmin()`, sonst HTTP 403
|
||
- Angewendet auf alle Admin-Routen in `routes/admin.php`
|
||
- Settings-Routen (`settings/profile`, `settings/password`, `settings/appearance`) aus der Admin-Gruppe herausgelöst – diese brauchen nur `auth`, kein Admin-Recht
|
||
|
||
**2. `MagicLinkConsumeController` – Eager-Load + Null-Check**
|
||
- `->with('user')` ergänzt: User wird in einer statt drei DB-Abfragen geladen
|
||
- Null-Check für `$magicLink->user` hinzugefügt: verhindert `TypeError` bei soft-deleted Usern
|
||
- Datei: `app/Http/Controllers/Auth/MagicLinkConsumeController.php`
|
||
|
||
**3. `UserRolePermissionSyncService` – Anti-Pattern entfernt**
|
||
- Direkte Permission-Zuweisung (`syncDirectPermissionsFromRoles`) komplett entfernt
|
||
- Rollen-Checks laufen ausschließlich über Spatie-Rollen (kein Doppel-Speichern)
|
||
- Methode jetzt: `assignRoleAndSyncPermissions()` = `$user->syncRoles([$role])`
|
||
- `DatabaseSeeder` unverändert funktionsfähig (Service-Interface bleibt gleich)
|
||
|
||
#### 🏗️ P2.5 – Portal-Scoping (Mandantentrennung)
|
||
|
||
**Neue Dateien:**
|
||
- `app/Services/CurrentPortalContext.php` – statischer Singleton für aktives Portal (get/set/clear)
|
||
- `app/Scopes/PortalScope.php` – Global Scope: filtert nach `portal = context OR portal = 'both'`; kein Filter wenn context = null (CLI, Tests, Import-Commands)
|
||
- `app/Http/Middleware/SetCurrentPortal.php` – setzt Portal-Kontext aus Session-Override (Admin-Wahl) oder Domain-Theme; registered in `bootstrap/app.php`
|
||
- `resources/views/livewire/admin/portal-switcher.blade.php` – Volt-Komponente für Sidebar-Portal-Filter (setzt `admin_portal_filter` Session-Key)
|
||
|
||
**PortalScope angewendet auf:**
|
||
- `App\Models\Company`
|
||
- `App\Models\Contact`
|
||
- `App\Models\PressRelease`
|
||
- `App\Models\NewsletterSubscription`
|
||
|
||
**Portal-Switcher** in `resources/views/components/layouts/app/sidebar.blade.php` eingebaut (nur für Admin-User sichtbar). Ermöglicht Filterung nach Presseecho / Businessportal24 / Alle.
|
||
|
||
#### 🐛 Bugfix: Billing-Address-Felder leerbar machen
|
||
|
||
Datei: `resources/views/livewire/admin/users/edit.blade.php` (Methode `save()`)
|
||
|
||
**Vorher:** `collect($billingPayload)->filter(fn ($v) => filled($v))` – leere Werte wurden silently ignoriert; Felder konnten einmal gesetzt nicht mehr geleert werden.
|
||
|
||
**Nachher:** Payload direkt ohne Merge übergeben. Pflichtfelder (name, address1, postal_code, city, country_code) werden auf filled() geprüft, optional Felder können geleert werden.
|
||
|
||
#### 🗄️ Migrations-Konsolidierung: `user_filter_presets`
|
||
|
||
Drei separate Migrations vom 2026-04-24 (120000 / 121500 / 123000) zu einer konsolidierten Migration zusammengeführt:
|
||
- `2026_04_24_120000_create_user_filter_presets_table.php` (ersetzt alle drei)
|
||
- Enthält: `id`, `user_id FK`, `page`, `name`, `is_default`, `last_used_at`, `filters JSON`, `timestamps`
|
||
- Alle vier Indizes in einer einzigen `Schema::create()`-Migration
|
||
|
||
#### ✉️ Session-Flash in Livewire-Inline-Actions ersetzt
|
||
|
||
`session()->flash()` in Inline-Actions (ohne Redirect) durch `$this->notification` Property ersetzt:
|
||
- `resources/views/livewire/admin/users/edit.blade.php` – 4 Inline-Actions (Firmen/Kontakt zuordnen/entfernen)
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` – 5 Inline-Actions (Preset CRUD, Contact-Delete)
|
||
|
||
Template zeigt jetzt eine Alpine.js-gesteuerte Auto-Dismiss-Meldung (3 Sekunden) für Erfolg und Fehler.
|
||
|
||
#### 🏭 Fehlende Factories ergänzt
|
||
|
||
Neue Factories:
|
||
- `database/factories/CompanyFactory.php` – mit States `presseecho()`, `businessportal24()`, `inactive()`
|
||
- `database/factories/ContactFactory.php`
|
||
- `database/factories/CategoryFactory.php` – mit `withTranslations()` State
|
||
- `database/factories/PressReleaseFactory.php` – mit States `published()`, `inReview()`, `forPortal()`
|
||
|
||
`HasFactory`-Trait + typisierte `@use`-PHPDoc ergänzt auf:
|
||
- `App\Models\Company`, `App\Models\Contact`, `App\Models\Category`, `App\Models\PressRelease`
|
||
|
||
**UserFactory** um die neuen Pflichtfelder erweitert (`portal`, `registration_type`, `language`, `is_active`, `is_super_admin`) – behebt Bug, bei dem `is_active` nach `User::factory()->create()` als `null` im Model-Attribut stand (DB-Defaults werden nicht automatisch in Attribute übernommen).
|
||
|
||
#### 🧪 Tests angepasst
|
||
|
||
- `tests/Pest.php`: `beforeEach()` räumt `CurrentPortalContext::clear()` auf (verhindert Static-State-Leakage zwischen Tests)
|
||
- `tests/Feature/Admin/UserManagementTest.php`: HTTP-GET-Test für User-Detailseite auf `LivewireVolt::test()` umgestellt (korrekte Art für Volt-Komponenten; verhindert Vite-Manifest-Fehler in Testumgebung)
|
||
|
||
### Verifikation
|
||
|
||
- `php artisan migrate:fresh --seed` ✅ (alle Migrations grün, Seeder grün)
|
||
- `php artisan test --compact tests/Feature/Admin/` ✅ (19 passed)
|
||
- `php artisan test --compact tests/Feature/Billing/ tests/Feature/Categories/ tests/Feature/Auth/UserAccessTest.php tests/Feature/Auth/MagicLinkLoginTest.php` ✅
|
||
- Gesamttestlauf: 47 passed, 13 failed (alle 13 Fehlschläge sind pre-existing Domain-Routing-Probleme in der Test-Umgebung, unverändert seit P1)
|
||
|
||
### Nächster Schritt
|
||
|
||
- P3 fortsetzen: PressRelease-CRUD auf echte Daten umstellen (derzeit Dummy-Daten)
|
||
- Categories-CRUD auf echte Daten umstellen
|
||
- Dashboard-Widgets mit echten Queries
|
||
- Phase 0.3: `.env` mit Legacy-DB-Connections konfigurieren (Voraussetzung für P6)
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Bugfix: Persistente Kontakt-Verknuepfung im User-Edit
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Sofort-Aktionen im User-Edit (`Zuordnen`/`Entfernen`) auch fuer Kontakte reload-stabil machen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Pivot-Tabelle `contact_user` eingefuehrt:
|
||
- Migration `2026_04_24_183000_create_contact_user_table.php`
|
||
- Many-to-many zwischen `users` und `contacts`
|
||
- Modelle erweitert:
|
||
- `App\Models\User::contacts()`
|
||
- `App\Models\Contact::users()`
|
||
- `resources/views/livewire/admin/users/edit.blade.php` angepasst:
|
||
- direkte Persistenz der Links ueber `persistUserLinks()` fuer Firmen **und** Kontakte
|
||
- `addLinkedContact()` / `removeLinkedContact()` schreiben jetzt sofort in `contact_user`
|
||
- beim Entfernen einer Firma werden zugehoerige Kontakt-Links ebenfalls bereinigt
|
||
- Initialbefuellung bleibt kompatibel (Fallback auf max. 40 Firmenkontakte, falls noch keine expliziten Kontaktlinks existieren)
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert/angepasst:
|
||
- Kontakt-Lookup testet jetzt auch Persistenz in `user->contacts()`
|
||
- neues Szenario `removing linked contact in user edit is persisted immediately`
|
||
- Testlauf:
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` ✅ (19 passed)
|
||
|
||
### Nächster Schritt
|
||
- Optional: In der UI die Anzahl explizit verknuepfter Kontakte anzeigen (Badge), getrennt von allen Firmenkontakten.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Bugfix: User-Edit Verknuepfungen direkt persistieren
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Firmen-/Kontakt-Zuordnung im User-Edit sofort in der DB speichern (ohne extra Save).
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/users/edit.blade.php` angepasst:
|
||
- neue interne Persistenz `persistCompanyLinks()` synchronisiert `user_companies` sofort
|
||
- `addLinkedCompany()` speichert Firmenverknuepfung direkt
|
||
- `removeLinkedCompany()` entfernt Firmenverknuepfung direkt
|
||
- `addLinkedContact()` speichert bei Bedarf die zugehoerige Firmenverknuepfung direkt
|
||
- direkte UI-Feedbacks per Flash (`direkt verknuepft` / `direkt entfernt`)
|
||
- damit ist kein zusaetzlicher Klick auf `Speichern` mehr noetig, um Zuordnen/Entfernen zu persistieren.
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert/angepasst:
|
||
- `removing linked company in user edit is persisted immediately`
|
||
- `contact lookup in user edit finds results from unlinked companies and auto-links company` (ohne Save)
|
||
- `adding linked company in user edit is persisted immediately`
|
||
- Testlauf:
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` ✅ (18 passed)
|
||
|
||
### Nächster Schritt
|
||
- Optional: Rollenwechsel pro Firmenkarte ebenfalls sofort persistieren (on-change), damit auch diese Aenderung ohne finalen Save direkt wirksam ist.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Bugfix: User-Edit Verknuepfung Entfernen + Suche
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Fehlerbehebung fuer User-Edit bei Entfernen von Verknuepfungen und Live-Suche.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/users/edit.blade.php` korrigiert:
|
||
- Kontakt-Lookup ist nicht mehr auf bereits verknuepfte Firmen eingeschraenkt
|
||
- beim Hinzufuegen eines Kontakts wird dessen Firma bei Bedarf automatisch als Firmenverknuepfung aufgenommen
|
||
- Action-Buttons (`Zuordnen`/`Entfernen`) explizit als `type=\"button\"`, damit kein unbeabsichtigter Form-Submit ausgeloest wird
|
||
- Suche bleibt performant: serverseitig, ab 2 Zeichen, max. 40 Treffer
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- `removing linked company in user edit is persisted after save`
|
||
- `contact lookup in user edit finds results from unlinked companies and auto-links company`
|
||
- Testlauf:
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` ✅ (17 passed)
|
||
|
||
### Nächster Schritt
|
||
- Optional: fuer bessere UX eine kleine Inline-Info anzeigen, wenn eine Kontakt-Zuordnung automatisch auch die Firma verknuepft hat.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3: User-Edit mit Live-Zuordnung fuer Firmen und Kontakte
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Firmen- und Kontaktverknuepfung im User-Edit auf performante Live-Suche/Select-Logik umstellen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/users/edit.blade.php` erweitert:
|
||
- neue Lookup-States fuer Firmen und Kontakte:
|
||
- `companyLookup`, `selectedLookupCompanyId`
|
||
- `contactLookup`, `selectedLookupContactId`
|
||
- neue Actions:
|
||
- `addLinkedCompany()`, `removeLinkedCompany()`
|
||
- `addLinkedContact()`, `removeLinkedContact()`
|
||
- `with()` liefert jetzt:
|
||
- `linkedCompanies` (nur aktuell verknuepfte Firmen)
|
||
- `companyLookupResults` (serverseitig gefiltert, max. 40 Treffer, ab 2 Zeichen)
|
||
- `contactLookupResults` (serverseitig gefiltert, max. 40 Treffer, ab 2 Zeichen)
|
||
- UI im User-Edit angepasst:
|
||
- Firmenverknuepfung ueber Live-Suche + Select + `Zuordnen`
|
||
- verknuepfte Firmen als Liste mit Rollenwahl + `Entfernen`
|
||
- Kontaktverknuepfung ueber Live-Suche + Select + `Zuordnen`
|
||
- verknuepfte Kontakte als editierbare Liste mit `Entfernen`
|
||
- `syncContactFormsToSelectedCompanies()` angepasst:
|
||
- laedt initial nicht mehr unlimitiert, sondern max. 40 Kontakte (Performance)
|
||
- haelt bestehend ausgewaehlte Kontakte konsistent, wenn Firmenzuordnung geaendert wird.
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- neues Szenario `admin can link companies and contacts in user edit via live lookup flow`
|
||
- prueft Live-Zuordnung von Firmen/Kontakt und korrektes Speichern inkl. Pivot-Rolle.
|
||
- Testlauf:
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` ✅ (15 passed)
|
||
|
||
### Nächster Schritt
|
||
- Optional: Asynchrone Suchresultate visuell nach Relevanz sortieren (Name-StartsWith vor Contains) fuer noch schnellere Zuordnung.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3: Company Contacts Tab mit Live-Select fuer bestehende Kontakte
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** In der Firmen-Detailansicht unter `Kontakte` bestehende Kontakte per Live-Suche finden und direkt der Firma zuordnen (performant, limitiert).
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/companies/show.blade.php` erweitert:
|
||
- neue State-Properties:
|
||
- `contactLookup` (Suchbegriff)
|
||
- `selectedExistingContactId` (ausgewaehlter Kontakt)
|
||
- neue Action `attachExistingContact()`:
|
||
- validiert Auswahl
|
||
- verschiebt Kontakt per `company_id` auf die aktuelle Firma
|
||
- behandelt Fehlerfaelle robust (Firma/Kontakt fehlt, bereits zugeordnet)
|
||
- Suchlogik in `with()`:
|
||
- serverseitiger Filter auf `first_name`, `last_name`, `email`
|
||
- Suche erst ab 2 Zeichen
|
||
- Ausschluss bereits zugeordneter Kontakte (`company_id != aktuelle Firma`)
|
||
- Ergebnislimit `40` zur Performance-Sicherung
|
||
- UI im Kontakte-Tab:
|
||
- Suchfeld mit Live-Debounce
|
||
- Select mit dynamischen Treffern
|
||
- Button `Zuordnen`
|
||
- Hinweistext zu Suchschwelle und Ergebnislimit
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um neues Szenario erweitert:
|
||
- `admin can live-search and assign existing contacts in company detail with result limit`
|
||
- prueft:
|
||
- Trefferliste wird bei vielen Treffern auf `40` limitiert
|
||
- ausgewaehlter Kontakt wird nach `attachExistingContact()` der Ziel-Firma zugeordnet.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Async-Select UX weiter verfeinern (z. B. Treffergruppierung nach Firma oder Tastatur-Navigation).
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3: Quick-Delete fuer Kontakte im Index
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Kontakte direkt in der Kontakte-Liste loeschbar machen, inkl. Warn-Modal und Soft-Delete.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` erweitert:
|
||
- neue Action `deleteContactFromIndex(int $contactId)`
|
||
- Soft Delete via `Contact::delete()` und Flash-Feedback
|
||
- Fehlerfall fuer nicht existente Kontakt-ID
|
||
- Pro Tabellenzeile neue Quick-Delete-UI:
|
||
- neues Trash-Icon in den Aktionen
|
||
- zeilenbezogenes Flux-Modal mit Warntext und expliziter Bestaetigung
|
||
- Bestaetigungsbutton ruft `deleteContactFromIndex(...)` auf
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- neues Szenario `admin can soft delete contact directly from contacts index flow`
|
||
- prueft, dass der Kontakt in der Standard-Query verschwindet und in `withTrashed()` als geloescht markiert bleibt.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Modal um Kontextdetails erweitern (Kontaktname/Firma), damit die Bestaetigung noch eindeutiger wird.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3: Contact Delete-Flow mit Warn-Modal
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Kontakte in der Admin-UI loeschbar machen und vor Loeschung per Modal explizit bestaetigen lassen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/contacts/edit.blade.php` erweitert:
|
||
- neue Action `deleteContact()` im Volt-Component
|
||
- Soft Delete via `Contact::delete()` inkl. Redirect + Flash-Meldung
|
||
- robuste Behandlung fuer nicht existente Kontakt-ID
|
||
- UI im Bereich "Aktionen" angepasst:
|
||
- Delete-Button oeffnet nun ein Flux-Modal statt direkter Loeschung
|
||
- Modal mit klarer Warnung und expliziter Bestaetigung (`Loeschung bestaetigen`)
|
||
- `Abbrechen` schliesst das Modal ohne Aktion
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- bestehendes Kontakt-CRUD-Szenario prueft nun zusaetzlich den Delete-Flow
|
||
- Assertions: Redirect auf `admin.contacts.index`, kein Treffer in Standard-Query, Datensatz in `withTrashed()` als geloescht vorhanden.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Delete-Aktion auch als Quick-Action im Contacts-Index mit row-spezifischem Modal ergaenzen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3: Company Delete-Flow mit Warn-Modal
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Loeschprozess fuer Firmen aktivieren und per bestaetigungspflichtigem Modal absichern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/companies/edit.blade.php` erweitert:
|
||
- neue Action `deleteCompany()` im Volt-Component
|
||
- Soft Delete via `Company::delete()` inkl. Redirect + Flash-Meldung
|
||
- robuste Behandlung fuer nicht existente Firmen-ID
|
||
- UI im Bereich "Aktionen" angepasst:
|
||
- Delete-Button oeffnet jetzt ein Flux-Modal statt direkt zu loeschen
|
||
- Modal zeigt klare Warnung und verlangt explizite Bestaetigung
|
||
- Buttons: `Abbrechen` und `Loeschung bestaetigen`
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- neues Szenario "admin can soft delete company from edit flow"
|
||
- prueft Redirect und dass Datensatz in Standard-Query verschwindet, aber in `withTrashed()` als geloescht vorhanden ist.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Beziehungen beim Loeschen sichtbar auflisten (z. B. Anzahl Kontakte/Benutzer im Modal), damit Admins den Impact vor Bestaetigung sehen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3: Companies-CRUD von Dummy auf DB umgestellt
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Firmenliste, Firmen-Erstellung und Firmen-Bearbeitung mit echten Eloquent-Daten anbinden.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/companies/index.blade.php` umgestellt:
|
||
- entfernt Dummy-Collection, ersetzt durch `Company`-Query mit `withCount(['pressReleases', 'contacts'])`
|
||
- Suche + Aktiv/Inaktiv-Filter auf DB-Ebene
|
||
- Pagination (`paginate(15)`) und Live-Statistiken (`total`, `active`, `inactive`)
|
||
- Tabellenzugriff auf echte Model-Attribute statt Array-Daten
|
||
- `resources/views/livewire/admin/companies/create.blade.php` umgestellt:
|
||
- `save()` erzeugt jetzt echte `Company`-Datensaetze
|
||
- Mapping auf vorhandene Spalten (`portal`, `type`, `name`, `slug`, `address`, `country_code`, `phone`, `email`, `website`, `logo_path`, `is_active`)
|
||
- eindeutige Slug-Generierung pro Portal
|
||
- Flux-Form um `Portal`- und `Typ`-Auswahl ergaenzt
|
||
- `resources/views/livewire/admin/companies/edit.blade.php` umgestellt:
|
||
- `mount()` laedt reale Firmendaten aus der DB und behandelt fehlende IDs robust per Redirect
|
||
- `update()` persistiert Aenderungen in der DB inkl. neuer Slug-Logik
|
||
- bestehendes Logo wird geladen/angezeigt, neues Logo wird bei Upload gespeichert
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- neues Szenario fuer Company Create + Edit (Persistenz inkl. `portal`, `type`, `slug`, `is_active`)
|
||
- Lokaler Testlauf:
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` ✅ (11 passed)
|
||
|
||
### Nächster Schritt
|
||
- Optional: Delete-Flow fuer Companies (Soft-Delete + UI-Aktion + Tests) aktivieren.
|
||
|
||
---
|
||
|
||
## Template
|
||
|
||
```markdown
|
||
## YYYY-MM-DD – <Titel>
|
||
|
||
**Phase:** <P1/…>
|
||
**Aufgaben:** <Kurzer Stichpunkt>
|
||
**Status:** ⬜ 🔄 ✅ ⏸️ ❌
|
||
|
||
### Was wurde gemacht
|
||
- …
|
||
|
||
### Probleme
|
||
- …
|
||
|
||
### Entscheidungen
|
||
- …
|
||
|
||
### Nächster Schritt
|
||
- …
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Migrationsplan aufgesetzt
|
||
|
||
**Phase:** 0
|
||
**Aufgaben:** Initiale Analyse + Dokumentationsordner `dev/migration 2026/` angelegt
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Legacy-Projekt `/_businessportal24.com/` systematisch untersucht (Struktur, Module, Datenmodell, Routen, Plugins)
|
||
- Aktuellen Laravel-Stack gesichtet (PHP 8.4, Laravel 12, Livewire 4, Volt, Flux 2, Fortify, Sanctum, Spatie Permission)
|
||
- Vorarbeiten gefunden & berücksichtigt:
|
||
- Admin-UI-Gerüst in `resources/views/admin/*` + `resources/views/livewire/admin/*`
|
||
- 40 deklarierte Admin-Routes in `routes/admin.php`
|
||
- Domain-basiertes Theme-System in `config/domains.php`
|
||
- Früheres Core-Package-Experiment unter `_businessportal24.com/dev/`
|
||
- Altbestand Migrations + Models in `dev/_old/`
|
||
- Acht Migrationsdokumente angelegt (README + 00–08)
|
||
|
||
### Entscheidungen (siehe README.md §D-01 … D-08)
|
||
- D-01 Neues DB-Schema (nicht 1:1 aus Doctrine)
|
||
- D-02 `portal`-Spalte für Mandanten-Disambiguierung
|
||
- D-03 Payment ausschließlich Stripe
|
||
- D-04 Country/Salutation als Config-Dateien
|
||
- D-05 API-Keys per Sanctum + Kompatibilitäts-Middleware
|
||
- D-06 Admin = Livewire Volt + Flux UI
|
||
- D-07 DE als primäre UI-Sprache
|
||
- D-08 Altes Symfony-Frontend wird nicht übernommen
|
||
|
||
### Offene Fragen (siehe 00-OVERVIEW.md §5)
|
||
- Q-01 DB-Dumps beider Portale vorhanden?
|
||
- Q-02 Legacy-Passwörter übernehmen?
|
||
- Q-03 API-Keys behalten?
|
||
- Q-04 Rechnungs-PDFs archivieren?
|
||
- Q-05 Media-Storage (S3 / lokal)?
|
||
- Q-06 Welche neuen Pflichtfelder?
|
||
- Q-07 Mehrsprachigkeit-Umfang?
|
||
- Q-08 PromotionLinks / FooterCode im Scope?
|
||
- Q-09 Coupons via Stripe oder intern?
|
||
- Q-10 Company/Agency trennen oder fusionieren?
|
||
|
||
### Nächster Schritt
|
||
- Auftraggeber-Review der Doku + Klärung der offenen Fragen
|
||
- Scope freigeben (siehe [`06-FEATURES-SCOPE.md`](./06-FEATURES-SCOPE.md) §13)
|
||
- Dann: Phase 1 starten (Migrations, Models, Factories, Seeders)
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Scope-Entscheidungen eingearbeitet (Q-01 … Q-10)
|
||
|
||
**Phase:** 0 → Übergang zu P1
|
||
**Aufgaben:** Antworten des Auftraggebers zu allen offenen Fragen als verbindliche Entscheidungen in die Dokumentation integriert.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
|
||
Alle 10 offenen Fragen sind mit den Antworten aus dem Kickoff beantwortet und als Entscheidungen **D-09 bis D-18** in `README.md` aufgenommen. Folgende Dokumente wurden überarbeitet:
|
||
|
||
- `00-OVERVIEW.md` – Fragen-Tabelle zu Entscheidungs-Tabelle, Nicht-Ziele + Phasen + Risiken + Erfolgskriterien aktualisiert
|
||
- `README.md` – D-09 … D-18, Dump-Infos (578 / 369 MB)
|
||
- `02-TARGET-ARCHITECTURE.md` – Magic-Link Auth, Customer-Portal, Rollen, Grandfathering, i18n DE+EN, Legacy-Invoice-Archiv
|
||
- `04-DATA-MODEL.md` – Legacy-Password/API-Key raus, `magic_links`-Tabelle, `legacy_invoices`-Archiv, `user_payment_options.grandfathered_until`, Company-Logo-Varianten, Promotion/Coupons entfernt
|
||
- `05-DATABASE-MERGE.md` – Dumps-Ist-Stand, Quellmodi (dump/direct/fixture), wiederholbares Skript, Go-Live-Runbook, Rehearsal-Pflicht
|
||
- `06-FEATURES-SCOPE.md` – neue IN/OUT/NEU-Matrix mit Customer-Portal §12, Rechnung-out, Promotion-out, Coupons-vertagt
|
||
- `07-API-MIGRATION.md` – Cut-over statt Keep-Alive, 410-Handler, Token-Abilities, Kommunikationsplan T-30/T-7/T-0
|
||
- `03-MIGRATION-PLAN.md` – Phasen auf 11 erweitert (P4 Customer-Portal neu, P8 Billing neu gebaut, P6 Daten-Migration als kritischer Pfad). Aufwand 16 → ~20 MT.
|
||
|
||
### Entscheidungen (neu)
|
||
- **D-09** Passwörter werden NICHT übernommen (Go-Live-Mailing)
|
||
- **D-10** Magic-Link-Login (Einmal-Passwort per Mail) ⭐
|
||
- **D-11** Backend = Admin + Customer-Portal ⭐
|
||
- **D-12** Legacy-Rechnungen → Archiv (`legacy_invoices`)
|
||
- **D-13** Neue Stripe-Produkte + Grandfathering für Alt-Kunden
|
||
- **D-14** Promotion Links entfallen
|
||
- **D-15** Newsletter wird neu aufgebaut
|
||
- **D-16** Coupons vertagt
|
||
- **D-17** `portal`-Spalte + `deleted_at` überall verpflichtend
|
||
- **D-18** Import als wiederholbares Skript (für Go-Live-Replay)
|
||
|
||
### Offene Inputs vom Auftraggeber
|
||
- Stripe-Test-Credentials + Live-Account
|
||
- Preisliste / Definition der neuen Stripe-Produkte
|
||
- Liste / Kriterien der "aktiven Alt-Abos" für Grandfathering
|
||
- Freigabe Rollen-Mapping `sfGuardGroup` → `admin`/`editor`/`customer`/`api-only`
|
||
- Liste der API-Key-Kunden für T-30-Mailing
|
||
|
||
### Nächster Schritt
|
||
- **Phase 0 abschließen:** lokale Schemas aus SQL-Dumps importieren, `.env` mit 3 DB-Connections konfigurieren
|
||
- **Phase 1 starten:** Migrations + Models + Factories + Seeders (ohne Coupons/PromotionLinks, mit `magic_links` + `legacy_invoices`)
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Dokumentationskonsolidierung (Unstimmigkeiten bereinigt)
|
||
|
||
**Phase:** 0 (Qualitätssicherung Doku)
|
||
**Aufgaben:** Konsistenzabgleich zwischen Migrationsdokumenten und aktuellem Projektstand.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `00-OVERVIEW.md`: API-Zieltext auf harten Cut-over vereinheitlicht (Sanctum-only, Legacy-Keys `410 Gone`).
|
||
- `README.md`: deklarierte Admin-Route-Anzahl von 40 auf 39 korrigiert.
|
||
- `resources/views/admin/BACKEND_STATUS.md`: Status von "komplett" auf "Gerüst weitgehend vorhanden" korrigiert; Route-Anzahl auf 39 angepasst; Liste fehlender Volt-Zielkomponenten ergänzt.
|
||
- `routes/ADMIN_ROUTES.md`: Kennzahl "Views erstellt" auf Basis 39 Routen korrigiert.
|
||
- `06-FEATURES-SCOPE.md`: Coupon-Zeile um Klarstellung ergänzt, dass bestehende Coupon-Routen/Views nur UI-Skeleton sind (nicht priorisiert in P1).
|
||
|
||
### Probleme
|
||
- Historische Statusdokumente enthalten teils veraltete Aussagen zum Umsetzungsgrad.
|
||
|
||
### Entscheidungen
|
||
- Für die API gilt verbindlich der dokumentierte Cut-over-Ansatz ohne Legacy-Key-Kompatibilität.
|
||
- Coupon-Funktion bleibt in Phase 1 vertagt; vorhandene Routen/Views haben keinen fachlichen Lieferanspruch.
|
||
|
||
### Nächster Schritt
|
||
- Optional: `routes/admin.php` und Volt-Dateistruktur in einem separaten Cleanup synchronisieren (fehlende Zielkomponenten ergänzen oder Routen temporär deaktivieren).
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Admin-Routing mit Volt-Struktur synchronisiert
|
||
|
||
**Phase:** 0 (Qualitätssicherung Doku/Struktur)
|
||
**Aufgaben:** `routes/admin.php` auf existierende Volt-Komponenten ausgerichtet; Route-Dokumentation aktualisiert.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- In `routes/admin.php` alle Volt-Routen entfernt, deren Zielkomponenten aktuell nicht existieren.
|
||
- Benutzer-Index-Route auf vorhandene Volt-Komponente gemappt (`admin.users` statt nicht vorhandenes `admin.users.index`).
|
||
- Aktive Admin-Routeanzahl auf **24** konsolidiert (nur valide Mappings).
|
||
- `routes/ADMIN_ROUTES.md` vollständig auf den aktuellen Stand neu geschrieben (aktive Routen + Backlog).
|
||
- `README.md` und `resources/views/admin/BACKEND_STATUS.md` auf den neuen, konsistenten Routenstand angepasst.
|
||
|
||
### Probleme
|
||
- Historischer Dokumentationsstand ging von vollständigem Routingset aus, obwohl mehrere Zielkomponenten nicht vorhanden waren.
|
||
|
||
### Entscheidungen
|
||
- Kurzfristig wurden keine Dummy-Volt-Komponenten erzeugt; stattdessen wurde das Routing auf tatsächlich vorhandene Komponenten reduziert.
|
||
|
||
### Nächster Schritt
|
||
- Bei Umsetzung neuer Admin-Bereiche (System, Create/Edit-Views, Detailseiten) die jeweiligen Routen gezielt wieder aktivieren.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Sidebar auf aktive Admin-Routen bereinigt
|
||
|
||
**Phase:** 0 (Qualitätssicherung UI-Navigation)
|
||
**Aufgaben:** Sidebar-Navigation mit aktuellem Routenstand synchronisiert.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- In `resources/views/components/layouts/app/sidebar.blade.php` die komplette System-Navigationsgruppe entfernt.
|
||
- Entfernte Links: `admin.scheduler.index`, `admin.newsletter.index`, `admin.settings` (waren nicht mehr im Routing aktiv).
|
||
- Verbleibende Sidebar-Links zeigen auf aktive Admin-Routen.
|
||
|
||
### Probleme
|
||
- Keine technischen Probleme; es handelte sich um UI-Navigation auf nicht mehr deklarierte Routen.
|
||
|
||
### Entscheidungen
|
||
- Nicht aktive Bereiche bleiben vorerst aus der Sidebar ausgeblendet, bis die zugehörigen Volt-Komponenten und Routen wieder eingeführt werden.
|
||
|
||
### Nächster Schritt
|
||
- Bei Reaktivierung von System-Bereichen die Sidebar-Gruppe gezielt wieder hinzufügen.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Legacy-Admin-Layouts auf gültige Route-Namen umgestellt
|
||
|
||
**Phase:** 0 (Qualitätssicherung UI-Navigation)
|
||
**Aufgaben:** Verbleibende Legacy-Admin-Layouts auf die aktuellen Route-Namen (`dashboard`, `settings.*`) synchronisiert.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/layouts/admin-master.blade.php` bereinigt:
|
||
- `admin.dashboard` → `dashboard`
|
||
- `admin.settings.profile` → `settings.profile`
|
||
- `admin.settings.appearance` → `settings.appearance`
|
||
- `resources/views/web/layouts/admin-master.blade.php` identisch bereinigt.
|
||
|
||
### Probleme
|
||
- Keine. Es handelte sich um konsistente Umbenennungen auf bestehende Route-Namen.
|
||
|
||
### Entscheidungen
|
||
- Auch wenig genutzte/legacy-nahe Layout-Dateien bleiben auf gültigem Routing-Stand, um spätere Regressionen zu vermeiden.
|
||
|
||
### Nächster Schritt
|
||
- Optional: prüfen, ob eines der beiden Legacy-Layouts noch aktiv verwendet wird; falls nicht, auf ein einziges Layout konsolidieren.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Admin-Layout konsolidiert (Single Source)
|
||
|
||
**Phase:** 0 (Qualitätssicherung UI-Struktur)
|
||
**Aufgaben:** Doppelte Admin-Layout-Dateien auf eine zentrale Quelle reduziert.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Nutzungssuche durchgeführt: keine aktiven Referenzen auf `web.layouts.admin-master` oder `layouts.admin-master` außerhalb der Doku gefunden.
|
||
- `resources/views/web/layouts/admin-master.blade.php` auf einen Alias reduziert:
|
||
- enthält jetzt nur noch `@extends('layouts.admin-master')`
|
||
- Damit ist `resources/views/layouts/admin-master.blade.php` die einzige echte Layout-Implementierung.
|
||
|
||
### Probleme
|
||
- Keine technischen Probleme; Konsolidierung wurde abwärtskompatibel umgesetzt.
|
||
|
||
### Entscheidungen
|
||
- Kompatibilitätsalias bleibt bestehen, um mögliche versteckte Verwendungen nicht zu brechen.
|
||
|
||
### Nächster Schritt
|
||
- Bei Gelegenheit prüfen, ob der Alias langfristig entfernt werden kann, sobald sicher ist, dass keine Legacy-Referenzen mehr existieren.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 gestartet (Enums, Config, erste Domain-Tabellen)
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Enums, Stammdaten-Config, Users-Extension, erste Domain-Migrationen und Models.
|
||
**Status:** 🔄
|
||
|
||
### Was wurde gemacht
|
||
- Enums unter `app/Enums` angelegt:
|
||
- `Portal`, `PressReleaseStatus`, `InvoiceStatus`, `PaymentOptionType`,
|
||
`UserPaymentOptionStatus`, `CompanyType`, `RegistrationType`, `PaymentStatus`
|
||
- Stammdaten als Config eingeführt:
|
||
- `config/countries.php`
|
||
- `config/salutations.php`
|
||
- Users-Fundament erweitert:
|
||
- neue Migration `expand_users_table_for_migration_2026`
|
||
- Felder wie `portal`, `registration_type`, `language`, `is_active`, `legacy_*`, `deleted_at`
|
||
- `password` auf nullable gesetzt
|
||
- entsprechendes Casting/Fillable in `User` ergänzt, inklusive Relations zu `Profile` und `MagicLink`
|
||
- Erste Domain-Tabellen erstellt:
|
||
- `profiles` (inkl. User-1:1 und Kernfeldern)
|
||
- `magic_links` (inkl. Purpose, Token-Hash, TTL, Single-Use-Felder)
|
||
- Modelle ergänzt:
|
||
- `app/Models/Profile.php`
|
||
- `app/Models/MagicLink.php`
|
||
- Spatie-Permission-Migration publiziert (`create_permission_tables`)
|
||
- Seeder auf P1-Rollenmodell umgestellt:
|
||
- Rollen: `admin`, `editor`, `customer`, `api-only`
|
||
- Basale API-/Admin-Permissions + Zuordnung
|
||
- `DatabaseSeeder` erstellt Admin-User und weist Rolle `admin` zu
|
||
|
||
### Probleme
|
||
- Globaler Testlauf zeigt weiterhin bestehende Routing-/Domain-Differenzen in Auth/Settings-Feature-Tests (404/Redirect), unabhängig von den neuen Phase-1-Artefakten.
|
||
- `vendor/bin/pint --dirty` war wegen vieler historischer Änderungen im Working Tree nicht sinnvoll; deshalb nur geänderte Dateien gezielt formatiert.
|
||
|
||
### Entscheidungen
|
||
- Soft-Deletes auf `users` bleiben gemäß Migrationsentscheidung aktiv; Account-Löschung wurde auf `forceDelete()` angepasst, damit der bestehende Löschtest weiterhin korrekt bleibt.
|
||
|
||
### Verifikation
|
||
- ✅ `php artisan test --compact --filter="user can delete their account"` (grün)
|
||
- ⚠️ `php artisan test --compact` aktuell nicht vollständig grün wegen bereits bestehender Routing-/Domain-Themen
|
||
|
||
### Nächster Schritt
|
||
- P1 fortsetzen mit weiteren Kernmigrationen (`companies`, `contacts`, `categories`, `press_releases`) und zugehörigen Basismodellen.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 erweitert: Companies & Contacts Fundament
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Companies/Contacts inkl. Pivot modellieren und migrierbar machen.
|
||
**Status:** 🔄
|
||
|
||
### Was wurde gemacht
|
||
- Neue Migrationen erstellt und umgesetzt:
|
||
- `create_companies_table`
|
||
- `create_company_user_table`
|
||
- `create_contacts_table`
|
||
- `companies` enthält u. a.:
|
||
- `portal`, `owner_user_id`, `type`, `name`, `slug`, Kontaktfelder, Logo-Felder, `legacy_*`, SoftDeletes
|
||
- Unique-Indizes auf `(portal, slug)` und `(legacy_portal, legacy_id)`
|
||
- `company_user` als Pivot umgesetzt:
|
||
- `company_id`, `user_id`, `role (member|responsible|owner)`, PK `(company_id, user_id)`
|
||
- `contacts` enthält u. a.:
|
||
- `company_id`, `portal`, Stammdatenfelder, `legacy_*`, SoftDeletes
|
||
- Unique auf `(legacy_portal, legacy_id)`
|
||
- Basismodelle ergänzt:
|
||
- `App\Models\Company` (Casts, Relations: owner/users/contacts)
|
||
- `App\Models\Contact` (Casts, Relation: company)
|
||
- `App\Models\User` erweitert um `ownedCompanies()` und `companies()` Relations
|
||
|
||
### Probleme
|
||
- Keine migrationsbezogenen Fehler im neuen Block.
|
||
|
||
### Verifikation
|
||
- ✅ `php artisan migrate:fresh --seed` läuft vollständig grün.
|
||
|
||
### Nächster Schritt
|
||
- P1 fortsetzen mit `categories` + `category_translations`, danach `press_releases` + `press_release_images` + Pivot-Tabellen.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 fokussiert: Newsletter-Subscriptions only
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Nur `newsletter_subscriptions` umsetzen und übrige Newsletter-nahe Tabellen vorerst aussetzen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Migration `create_newsletter_subscriptions_table` umgesetzt:
|
||
- Felder gemäß Datenmodell (`portal`, `user_id`, Name/Kontakt, `is_confirmed`, `confirmation_token`, `subscribed_at`, `unsubscribed_at`, `legacy_*`)
|
||
- Constraints/Indizes ergänzt:
|
||
- Unique `(portal, email)`
|
||
- Unique `confirmation_token`
|
||
- Unique `(legacy_portal, legacy_id)`
|
||
- Index `(portal, is_confirmed)`
|
||
- Neues Basismodell `App\Models\NewsletterSubscription` angelegt:
|
||
- Fillable + Casts (`portal`, `is_confirmed`, Zeitfelder)
|
||
- Relation `user()`
|
||
- `App\Models\User` um Relation `newsletterSubscriptions()` erweitert.
|
||
- Dokumentation aktualisiert:
|
||
- `03-MIGRATION-PLAN.md` um Scope-Hinweis ergänzt (Newsletter-Block nur `newsletter_subscriptions`).
|
||
|
||
### Entscheidungen
|
||
- Auf Wunsch des Auftraggebers wird der Newsletter-Bereich in P1 bewusst reduziert.
|
||
- `blacklists`, `footer_codes` und `category_footer_code` werden aktuell **nicht** umgesetzt.
|
||
- Geplante externe Newsletter-Synchronisierung erfolgt später über separate API-Integration.
|
||
|
||
### Verifikation
|
||
- ✅ Migration läuft in bestehender Datenbank durch.
|
||
- ✅ Code-Formatierung und Lint ohne neue Fehler.
|
||
|
||
### Nächster Schritt
|
||
- Nächsten priorisierten P1-Baustein nur nach expliziter Freigabe umsetzen (kein automatisches Nachziehen der ausgesetzten Tabellen).
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Newsletter-Sync Skeleton vorbereitet
|
||
|
||
**Phase:** P1 (Vorbereitung externe Integration)
|
||
**Aufgaben:** Technisches Grundgeruest fuer spaetere API-Synchronisierung anlegen, ohne aktive Provider-Implementierung.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Contract fuer externe Newsletter-Synchronisierung eingefuehrt:
|
||
- `App\Contracts\NewsletterSyncClient` (`subscribe()` / `unsubscribe()`)
|
||
- Platzhalter-Implementierung erstellt:
|
||
- `App\Services\Newsletter\NullNewsletterSyncClient`
|
||
- Orchestrierungsservice erstellt:
|
||
- `App\Services\Newsletter\NewsletterSyncService`
|
||
- entscheidet anhand von `is_confirmed` und `unsubscribed_at`, ob subscribe oder unsubscribe ausgeloest wird
|
||
- Container-Binding in `AppServiceProvider` ergaenzt:
|
||
- `NewsletterSyncClient::class` -> `NullNewsletterSyncClient::class`
|
||
- Konfigurationsdatei fuer spaetere externe Anbindung hinzugefuegt:
|
||
- `config/newsletter.php` (`enabled`, `provider`, `endpoint`, `api_key`, `timeout`)
|
||
|
||
### Entscheidungen
|
||
- Es wurde bewusst kein externer API-Client angebunden.
|
||
- Der aktuelle Stand ist ein sicherer No-Op, bis Provider und Credentials freigegeben sind.
|
||
|
||
### Nächster Schritt
|
||
- Bei Freigabe des externen Dienstes einen echten Adapter (z. B. `...Client`) implementieren und nur das Binding austauschen.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Newsletter-Sync Admin-View + Navigation ergänzt
|
||
|
||
**Phase:** P1 (Admin-Oberflaeche)
|
||
**Aufgaben:** Bedienoberflaeche fuer Newsletter-Synchronisierung sichtbar machen und in Navigation integrieren.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Volt-Route hinzugefuegt:
|
||
- `GET /admin/newsletter-sync` -> `admin.newsletter.sync`
|
||
- Neue Livewire-Volt-Seite erstellt:
|
||
- `resources/views/livewire/admin/newsletter/sync.blade.php`
|
||
- Enthält Uebersicht zu Newsletter-Status (gesamt, bestaetigt, unbestaetigt, abgemeldet)
|
||
- Zeigt aktuelle Sync-Konfiguration aus `config/newsletter.php`
|
||
- Sidebar erweitert:
|
||
- Unterpunkt `Newsletter Sync` im Bereich **Billing** hinzugefuegt.
|
||
- Routen-Dokumentation aktualisiert:
|
||
- `routes/ADMIN_ROUTES.md` auf 25 aktive Routen angepasst.
|
||
|
||
### Entscheidungen
|
||
- Die Seite dient aktuell als Monitoring-/Konfigurationsansicht fuer das vorbereitete Sync-Skeleton.
|
||
- Keine aktive externe API-Kommunikation auf dieser Seite; Provider bleibt weiterhin als No-Op gebunden.
|
||
|
||
### Nächster Schritt
|
||
- Optional: manuellen Test-Button fuer "Einzelnen Datensatz synchronisieren" erst nach Freigabe des externen API-Vertrags einfuehren.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Newsletter-Sync UI abgeschlossen (Test + Dry-Run)
|
||
|
||
**Phase:** P1 (Admin-Oberflaeche)
|
||
**Aufgaben:** Newsletter-Sync-Seite funktional abschliessen, ohne externe API-Kommunikation.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- In `resources/views/livewire/admin/newsletter/sync.blade.php` zwei UI-Aktionen finalisiert:
|
||
- `Test-Sync ausfuehren` (No-Op-Serviceaufruf ueber `NewsletterSyncService`)
|
||
- `Dry Run` (Vorschau ohne Serviceaufruf)
|
||
- Rueckmeldungen auf der Seite ergaenzt (`syncMessage`, `dryRunMessage`), inkl. Zielaktion (`subscribe` / `unsubscribe`) und Datensatzbezug.
|
||
- Damit ist der angeforderte Newsletter-Sync-Bedienpunkt inkl. Navigation und Test-Interaktion abgeschlossen.
|
||
|
||
### Entscheidungen
|
||
- Funktionalitaet bleibt absichtlich provider-neutral (kein echter API-Call), bis externer Dienst final freigegeben ist.
|
||
|
||
### Nächster Schritt
|
||
- Phase 1 mit Billing-/Payment-Fundament fortsetzen.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 erweitert: Billing-Adressfundament
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Rechnungsadressen fuer Benutzer und Rechnungssnapshots modellieren.
|
||
**Status:** 🔄
|
||
|
||
### Was wurde gemacht
|
||
- Neue Migrationen umgesetzt:
|
||
- `create_billing_addresses_table`
|
||
- `create_invoice_billing_addresses_table`
|
||
- `billing_addresses` enthaelt:
|
||
- `user_id` (FK, unique), Anrede-/Adressfelder, `country_code`
|
||
- `invoice_billing_addresses` enthaelt:
|
||
- Snapshot-Adressfelder ohne `user_id`-FK (fuer historische Rechnungsdaten)
|
||
- Basismodelle angelegt:
|
||
- `App\Models\BillingAddress` inkl. `user()`-Relation
|
||
- `App\Models\InvoiceBillingAddress`
|
||
- `App\Models\User` um `billingAddress()` erweitert (1:1).
|
||
|
||
### Entscheidungen
|
||
- `billingAddress` ist als 1:1-Relation ausgelegt (technisch via Unique-FK abgesichert).
|
||
|
||
### Nächster Schritt
|
||
- P1 fortsetzen mit `payment_options` + `payment_option_translations` und anschliessend `user_payment_options`.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 erweitert: Payment-Options Fundament
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Produktkatalog fuer Zahlungen inkl. Uebersetzungen modellieren.
|
||
**Status:** 🔄
|
||
|
||
### Was wurde gemacht
|
||
- Neue Migrationen umgesetzt:
|
||
- `create_payment_options_table`
|
||
- `create_payment_option_translations_table`
|
||
- `payment_options` enthaelt:
|
||
- `article_number` (unique), `type` (Enum recurring/onetime), `price_cents`, `currency`, `interval`, `is_hidden`, Stripe-IDs
|
||
- SoftDeletes und relevante Indizes
|
||
- `payment_option_translations` enthaelt:
|
||
- FK auf `payment_options`, `locale`, `name`, `description`
|
||
- Unique auf `(payment_option_id, locale)`
|
||
- Basismodelle angelegt:
|
||
- `App\Models\PaymentOption` (Casts inkl. `PaymentOptionType`, Relation `translations()`)
|
||
- `App\Models\PaymentOptionTranslation` (Relation `paymentOption()`)
|
||
|
||
### Entscheidungen
|
||
- `interval` bleibt als fachliches Enum-Feld (`monthly`, `yearly`, `once`) in der DB, bis ein separates PHP-Enum benoetigt wird.
|
||
|
||
### Nächster Schritt
|
||
- P1 fortsetzen mit `user_payment_options` + `user_payment_option_company`, danach `user_payments`.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 erweitert: User-Payment Fundament
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Aktive Abos, Firmenzuordnung und Zahlungen modellieren.
|
||
**Status:** 🔄
|
||
|
||
### Was wurde gemacht
|
||
- Neue Migrationen umgesetzt:
|
||
- `create_user_payment_options_table`
|
||
- `create_user_payment_option_company_table`
|
||
- `create_user_payments_table`
|
||
- `user_payment_options` enthaelt:
|
||
- FK `user_id`, FK `payment_option_id`
|
||
- `status` (Enum `active|past_due|cancelled|grandfathered`)
|
||
- `grandfathered_until`, `legacy_conditions` (JSON)
|
||
- `current_period_start`, `current_period_end`, `stripe_subscription_id`, `cancelled_at`
|
||
- `user_payment_option_company` als Pivot umgesetzt:
|
||
- `user_payment_option_id`, `company_id`, `is_active`, `timestamps`
|
||
- zusammengesetzter Primary Key auf beiden IDs
|
||
- `user_payments` enthaelt:
|
||
- optionale FK `user_payment_option_id`
|
||
- `amount_cents`, `currency`, `status` (Enum `pending|succeeded|failed|refunded`)
|
||
- `stripe_charge_id`, `stripe_invoice_id`
|
||
- Basismodelle angelegt/erweitert:
|
||
- `App\Models\UserPaymentOption` (Casts, Relations: user, paymentOption, companies, payments)
|
||
- `App\Models\UserPayment` (Casts, Relation: userPaymentOption)
|
||
- `App\Models\PaymentOption` erweitert um `userPaymentOptions()`
|
||
- `App\Models\Company` erweitert um `userPaymentOptions()`
|
||
- `App\Models\User` erweitert um `userPaymentOptions()`
|
||
|
||
### Entscheidungen
|
||
- Pivot `user_payment_option_company` bleibt bewusst als Composite-Key-Tabelle ohne eigene `id`.
|
||
- Stripe-IDs werden als nullable Felder gefuehrt und initial nur indexiert.
|
||
|
||
### Nächster Schritt
|
||
- P1 fortsetzen mit `invoices` + `legacy_invoices` + `legacy_import_map`.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 erweitert: Invoice- und Importfundament
|
||
|
||
**Phase:** P1 (Fundament)
|
||
**Aufgaben:** Neuer Rechnungskreis, Legacy-Rechnungsarchiv und Import-Tracking modellieren.
|
||
**Status:** 🔄
|
||
|
||
### Was wurde gemacht
|
||
- Neue Migrationen umgesetzt:
|
||
- `create_invoices_table`
|
||
- `create_legacy_invoices_table`
|
||
- `create_legacy_import_map_table`
|
||
- `invoices` enthaelt:
|
||
- FK `user_id`, optionale FK `user_payment_id`, FK `invoice_billing_address_id`
|
||
- `number`, `status`, Betragsfelder (`amount_cents`, `tax_cents`, `total_cents`)
|
||
- `currency`, `is_netto`, `invoice_date`, `due_date`, `paid_at`
|
||
- `stripe_invoice_id`, `pdf_path`, SoftDeletes + Indizes
|
||
- `legacy_invoices` als read-only Archiv enthaelt:
|
||
- `legacy_portal`, `legacy_id` (unique kombiniert), optionale FK `user_id`, `legacy_user_id`
|
||
- Nummer, Betrags- und Datumsfelder, `payment_method`, `pdf_path`, `raw_snapshot`, `imported_at`
|
||
- `legacy_import_map` enthaelt:
|
||
- `legacy_portal`, `legacy_table`, `legacy_id`, `target_table`, `target_id`, `imported_at`
|
||
- Unique auf `(legacy_portal, legacy_table, legacy_id)`
|
||
- Basismodelle angelegt/erweitert:
|
||
- `App\Models\Invoice` (Casts inkl. `InvoiceStatus`, Relations: user, userPayment, invoiceBillingAddress)
|
||
- `App\Models\LegacyInvoice` (read-only via `timestamps=false`, Casts inkl. `raw_snapshot`)
|
||
- `App\Models\LegacyImportMap` (Table-Mapping auf `legacy_import_map`, `timestamps=false`)
|
||
- `App\Models\UserPayment` erweitert um `invoices()`
|
||
- `App\Models\InvoiceBillingAddress` erweitert um `invoices()`
|
||
- `App\Models\User` erweitert um `invoices()` und `legacyInvoices()`
|
||
|
||
### Entscheidungen
|
||
- `legacy_invoices` und `legacy_import_map` werden ohne `created_at/updated_at` gefuehrt; Zeitbezug erfolgt ueber `imported_at`.
|
||
- `legacy_portal` bleibt als Enum-Feld im Datenmodell, um die Herkunft eindeutig und validierbar zu halten.
|
||
|
||
### Nächster Schritt
|
||
- P1 Restblock vorbereiten: Factories/Seeder-Ergaenzungen und anschliessend `migrate:fresh --seed` Volltest.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Phase 1 validiert: Factory-/Seeder-Erweiterung + Full Reset
|
||
|
||
**Phase:** P1 (Qualitaetssicherung)
|
||
**Aufgaben:** Factories und Seeder fuer den neuen Billing-/Invoice-Block ergaenzen und End-to-End validieren.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Factories angelegt und befuellt:
|
||
- `PaymentOptionFactory`
|
||
- `PaymentOptionTranslationFactory`
|
||
- `UserPaymentOptionFactory`
|
||
- `UserPaymentFactory`
|
||
- `BillingAddressFactory`
|
||
- `InvoiceBillingAddressFactory`
|
||
- `InvoiceFactory`
|
||
- Neue Seeder-Klasse umgesetzt:
|
||
- `PaymentOptionSeeder` mit idempotentem `updateOrCreate` (inkl. DE/EN-Translations)
|
||
- `DatabaseSeeder` erweitert:
|
||
- ruft jetzt `PaymentOptionSeeder` zusaetzlich auf.
|
||
- `HasFactory` auf relevante Modelle ergaenzt, damit die neuen Factories konsistent nutzbar sind.
|
||
|
||
### Verifikation
|
||
- ✅ `php artisan migrate:fresh --seed` laeuft vollstaendig grün.
|
||
- ✅ Alle aktuellen P1-Migrationen werden beim Full-Reset erfolgreich aufgebaut.
|
||
- ✅ Seeder-Lauf inkl. Rollen/Berechtigungen und Payment-Optionen erfolgreich.
|
||
|
||
### Nächster Schritt
|
||
- Optionaler Abschlusslauf mit zielgerichteten Feature-Tests fuer Billing/Invoice-Flows.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Fehlende Admin-View ergänzt (Roles Edit)
|
||
|
||
**Phase:** P1 (Admin-Oberflaeche / Konsistenz)
|
||
**Aufgaben:** Fehlende Volt-Zielkomponente fuer aktive Route nachziehen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Fehlende View `resources/views/livewire/admin/roles/edit.blade.php` erstellt.
|
||
- Die aktive Route `admin.roles.edit` zeigt jetzt auf eine vorhandene Volt-Komponente.
|
||
- Komponente enthaelt:
|
||
- Laden der echten Spatie-Rolle inkl. bestehender Permissions
|
||
- Validierung mit Unique-Check (guard-spezifisch) und Permission-Existenzpruefung
|
||
- Speichern mit `syncPermissions()` auf der Rolle
|
||
- Warnhinweis fuer Systemrollen (`admin`, `editor`, `customer`, `api-only`)
|
||
|
||
### Entscheidungen
|
||
- UI ist konsistent mit dem bestehenden Rollen-Create-Pattern.
|
||
- Persistenz fuer Rollenname und Permissions ist produktiv auf Spatie-Basis angebunden.
|
||
|
||
### Nächster Schritt
|
||
- Optional: `roles/create` ebenfalls auf echten Spatie-Flow umstellen (analog zu `roles/edit`).
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Rollenverwaltung konsolidiert (Create auf Spatie umgestellt)
|
||
|
||
**Phase:** P1 (Admin-Oberflaeche / Konsistenz)
|
||
**Aufgaben:** `roles/create` auf dieselbe produktive Spatie-Logik wie `roles/edit` umstellen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/roles/create.blade.php` komplett auf echte Spatie-Daten umgestellt:
|
||
- Role-Erstellung via `Role::create(...)`
|
||
- Permission-Validierung via `Rule::exists(...)` guard-spezifisch
|
||
- Permission-Sync via `syncPermissions(...)`
|
||
- Live-Gruppierung der Permissions aus DB statt statischer Demo-Listen
|
||
- Nicht persistente Demo-Felder (`display_name`, `description`, `color`) entfernt, da sie nicht zum Spatie-Rollenmodell gehoeren.
|
||
|
||
### Entscheidungen
|
||
- Rollenverwaltung (Create/Edit) folgt jetzt einheitlich dem minimalen Spatie-Datenmodell (`name`, `guard_name`, Permissions).
|
||
- Zusätzliche Metadaten fuer Rollen bleiben optionales Folgefeature und sind aktuell nicht Teil des Schemas.
|
||
|
||
### Nächster Schritt
|
||
- Optional: `roles/index` von statischen Demo-Daten auf echte Spatie-Queries umstellen.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Rollenübersicht auf echte Spatie-Daten umgestellt
|
||
|
||
**Phase:** P1 (Admin-Oberflaeche / Konsistenz)
|
||
**Aufgaben:** `roles/index` von statischer Demo-Liste auf produktive Rollen-/Permission-Daten umstellen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/roles/index.blade.php` umgestellt auf echte Queries:
|
||
- Rollen aus `Spatie\Permission\Models\Role`
|
||
- `withCount(['users', 'permissions'])` fuer Live-Kennzahlen
|
||
- Tabellen-Rendering auf echte Role-Objekte angepasst (`$role->...` statt Array-Mockdaten).
|
||
- System-/Custom-Tagging anhand der Basisrollen (`admin`, `editor`, `customer`, `api-only`) beibehalten.
|
||
- Empty-State fuer den Fall ohne Rollen ergaenzt.
|
||
|
||
### Entscheidungen
|
||
- Anzeige bleibt bewusst nah am bisherigen UI, aber Datenquelle ist jetzt voll produktiv.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Rollenname-Labeling (z. B. lokalisierte Anzeigenamen) ueber separate Mapping-Konfiguration verfeinern.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Billing/Invoice Feature-Tests ergänzt
|
||
|
||
**Phase:** P1 (Qualitaetssicherung)
|
||
**Aufgaben:** Kernbeziehungen und Seeder-Integritaet im neuen Billing-/Invoice-Block testseitig absichern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neuer Pest-Test angelegt:
|
||
- `tests/Feature/Billing/BillingModelsTest.php`
|
||
- Abgedeckte Szenarien:
|
||
- `PaymentOptionSeeder` erzeugt die erwarteten multilingualen Payment-Optionen
|
||
- `UserPaymentOption` Pivot- und Payment-Relationen funktionieren inkl. Persistenz
|
||
- `Invoice`-Factory erzeugt gueltigen Relation-Graph (`user`, `invoiceBillingAddress`, `userPayment`)
|
||
|
||
### Entscheidungen
|
||
- Fokus auf schnelle Integritaets-Checks entlang der wichtigsten neuen P1-Modelle.
|
||
- Tests bleiben datenbanknah (Feature-Ebene mit `RefreshDatabase`) fuer hohe Aussagekraft.
|
||
|
||
### Nächster Schritt
|
||
- Optional: weitere Feature-Tests fuer Rollenverwaltung (Create/Edit/Sync) und Newsletter-Sync-UI-Flow.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Permissions zusaetzlich Usern zugeordnet
|
||
|
||
**Phase:** P1 (Auth/Rollen-Konsistenz)
|
||
**Aufgaben:** Sicherstellen, dass Basis-User neben Rollen auch direkte Permission-Zuordnung erhalten.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `database/seeders/DatabaseSeeder.php` angepasst:
|
||
- Admin-User erhaelt Rolle `admin` **und** direkte Permissions aus `getPermissionsViaRoles()`
|
||
- Test-User erhaelt Rolle `customer` **und** direkte Permissions aus `getPermissionsViaRoles()`
|
||
|
||
### Entscheidungen
|
||
- Permissions werden bewusst doppelt abgesichert (Rollenvererbung + direkte Zuordnung), um Zugriffsverhalten fuer Basis-Accounts eindeutig zu halten.
|
||
|
||
### Nächster Schritt
|
||
- Optional: gleiche Logik fuer weitere Seed-/Import-User anwenden, wenn diese initial mit Rollen angelegt werden.
|
||
|
||
---
|
||
|
||
## 2026-04-23 – Rollen/Permissions-Sync fuer Importflow vorbereitet
|
||
|
||
**Phase:** P1 (Auth/Rollen-Konsistenz)
|
||
**Aufgaben:** Rollen- und Permission-Synchronisierung in einen wiederverwendbaren Service auslagern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neuer Service angelegt:
|
||
- `App\Services\Auth\UserRolePermissionSyncService`
|
||
- Methoden:
|
||
- `assignRoleAndSyncPermissions(User $user, string $role)`
|
||
- `syncDirectPermissionsFromRoles(User $user)`
|
||
- `DatabaseSeeder` auf den Service umgestellt:
|
||
- Admin-/Test-User erhalten Rollen und direkte Permissions jetzt ueber die zentrale Service-Logik.
|
||
|
||
### Entscheidungen
|
||
- Service dient als zentrale Stelle fuer denselben Mechanismus im kuenftigen Legacy-Import.
|
||
- Damit wird verhindert, dass Seeder und spaetere Import-Commands divergierendes Rollen-/Permission-Verhalten haben.
|
||
|
||
### Nächster Schritt
|
||
- Bei Implementierung der Legacy-Import-Commands denselben Service direkt nach dem Rollen-Mapping aufrufen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – Phase 2 gestartet: User-Access-Gates im Model
|
||
|
||
**Phase:** P2 (Auth & Tenancy)
|
||
**Aufgaben:** `canAccessAdmin()` und `canAccessCustomer()` im `User`-Model einfuehren und testseitig absichern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `App\Models\User` erweitert:
|
||
- `canAccessAdmin()` implementiert (aktiv + Rolle `admin|editor` oder `is_super_admin`)
|
||
- `canAccessCustomer()` implementiert (aktiv + Rolle `admin|editor|customer`)
|
||
- Neuer Pest-Feature-Test `tests/Feature/Auth/UserAccessTest.php` erstellt:
|
||
- positive und negative Faelle fuer Admin-/Customer-Zugriff
|
||
- Inaktiv- und `api-only`-Faelle explizit abgedeckt
|
||
- `is_super_admin`-Bypass fuer Admin-Zugang verifiziert
|
||
|
||
### Entscheidungen
|
||
- Zugriffsmethoden liegen zentral im `User`-Model, damit UI-/Policy-Code dieselbe Logik nutzt.
|
||
- `api-only` bleibt explizit ohne Customer-Portal-Zugriff.
|
||
|
||
### Nächster Schritt
|
||
- P2.3 umsetzen: Magic-Link-Flow (`Generator`, `Consume`, `Mailable`) auf Basis der bereits vorhandenen `magic_links`-Tabelle starten.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P1 nachgezogen: CategorySeeder + Tests
|
||
|
||
**Phase:** P1 (Fundament / Seeder-Block)
|
||
**Aufgaben:** Fehlenden Kategorie-Seed (`DE/EN`) aus dem Plan umsetzen und absichern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neuer Seeder `database/seeders/CategorySeeder.php` angelegt:
|
||
- initialisiert drei Basiskategorien
|
||
- legt pro Kategorie DE/EN-Translation an
|
||
- arbeitet idempotent ueber vorhandene DE-Slugs
|
||
- `database/seeders/DatabaseSeeder.php` erweitert:
|
||
- `CategorySeeder` wird im Standard-Seedlauf ausgefuehrt
|
||
- Neuer Pest-Test `tests/Feature/Categories/CategorySeederTest.php`:
|
||
- prueft Erstellung mehrsprachiger Kategorien
|
||
- prueft idempotentes Verhalten bei doppeltem Seeder-Lauf
|
||
|
||
### Entscheidungen
|
||
- Kategorien werden aktuell ueber stabile DE-Slugs erkannt, solange kein separates Legacy-Mapping fuer Taxonomie aktiv ist.
|
||
|
||
### Nächster Schritt
|
||
- Offene Seeder-Restpunkte aus P1 (z. B. Salutation-Translations) separat priorisieren oder direkt P2.3 (Magic-Link-Flow) starten.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P2 umgesetzt: Magic-Link-Login End-to-End
|
||
|
||
**Phase:** P2 (Auth & Tenancy)
|
||
**Aufgaben:** Magic-Link-Login (Generierung, Versand, Konsumierung) inkl. Login-UI-Erweiterung und Feature-Tests.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neuer Service `App\Services\Auth\MagicLinkGenerator`:
|
||
- erzeugt One-Time-Token (`sha256` gehasht in DB)
|
||
- invalidiert alte, noch offene Login-Links des Users
|
||
- setzt TTL und Request-IP
|
||
- Neuer Controller `App\Http\Controllers\Auth\MagicLinkConsumeController`:
|
||
- prueft Token, Ablauf und Single-Use
|
||
- markiert Link als konsumiert inkl. IP
|
||
- authentifiziert User ueber `web`-Guard und regeneriert Session
|
||
- Neue Mailable `App\Mail\MagicLoginLink` + Template `resources/views/emails/auth/magic-login-link.blade.php`.
|
||
- Auth-Routing erweitert:
|
||
- `GET /magic-login/{token}` (`magic-links.consume`)
|
||
- Login-Volt-Komponente (`resources/views/livewire/auth/login.blade.php`) erweitert:
|
||
- Methode `sendMagicLink()`
|
||
- UI-Block fuer "Login ohne Passwort" per E-Mail-Link
|
||
- Neue Feature-Tests:
|
||
- `tests/Feature/Auth/MagicLinkLoginTest.php`
|
||
- Faelle: Request, gueltiger Link, abgelaufener Link, bereits konsumierter Link
|
||
|
||
### Entscheidungen
|
||
- Magische Login-Links sind bewusst single-use und zeitlich begrenzt.
|
||
- Bei unbekannter/inaktiver E-Mail wird eine neutrale Erfolgsmeldung ausgegeben, um Enumeration zu erschweren.
|
||
|
||
### Nächster Schritt
|
||
- P2.5 starten: `SetCurrentPortal`-Middleware + `CurrentPortalScope` (Global Scope) fuer mandantenbezogene Datensaetze.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 vorgezogen: User-Edit mit Rollen, Firmen, Kontakten und Rechnungsadresse
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Benutzerbearbeitung von Demo-/Listenstatus auf produktive Persistenz erweitern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Volt-Komponente `resources/views/livewire/admin/users/edit.blade.php` erstellt:
|
||
- Basisdaten des Users editierbar (`name`, `email`, `portal`, `registration_type`, Statusflags)
|
||
- Rollenzuweisung via Spatie (`syncRoles`)
|
||
- Firmenverknuepfung inkl. Pivot-Rolle (`member|responsible|owner`) via `company_user`
|
||
- Sichtbare und editierbare Kontakte der verknuepften Firmen
|
||
- Bearbeitbare Rechnungsadresse (`billing_addresses`) per `updateOrCreate`
|
||
- Routing erweitert:
|
||
- neue Route `admin.users.edit` (`/admin/users/{id}/edit`)
|
||
- User-Liste erweitert (`resources/views/livewire/admin/users.blade.php`):
|
||
- Rollen und Firmenanzahl sichtbar
|
||
- Edit-Aktion pro Benutzer
|
||
- Routing-Doku aktualisiert:
|
||
- `routes/ADMIN_ROUTES.md` auf 26 aktive Routen inkl. `admin.users.edit`
|
||
|
||
### Verifikation
|
||
- Neuer Pest-Test `tests/Feature/Admin/UserManagementTest.php`:
|
||
- prueft End-to-End: User-Update, Rollen-Sync, Firmen-Pivot-Rollen, Kontakt-Update, Rechnungsadresse
|
||
|
||
### Entscheidungen
|
||
- Kontaktbearbeitung erfolgt im ersten Schritt direkt im User-Edit-Kontext fuer alle verknuepften Firmenkontakte.
|
||
- Eigene Kontakt-CRUD-Routen bleiben weiterhin Backlog und koennen spaeter separat aktiviert werden.
|
||
|
||
### Nächster Schritt
|
||
- Optional: `admin.users.create` + Detailansicht nachziehen, um den User-CRUD in P3 vollstaendig zu machen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 erweitert: User-Create nachgezogen
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** User-CRUD vervollstaendigen mit Anlegen-Flow inkl. Rollen/Firmen/Billing.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Volt-Komponente `resources/views/livewire/admin/users/create.blade.php` erstellt:
|
||
- Anlegen von Usern mit `portal`, `registration_type`, Statusflags
|
||
- Rollenzuweisung via Spatie (`syncRoles`)
|
||
- Firmenverknuepfung inkl. Pivot-Rolle (`member|responsible|owner`)
|
||
- Optionale Rechnungsadresse direkt beim Anlegen
|
||
- Routing erweitert:
|
||
- neue Route `admin.users.create` (`/admin/users/create`)
|
||
- User-Index erweitert:
|
||
- CTA-Button "Benutzer anlegen" in `resources/views/livewire/admin/users.blade.php`
|
||
- Routing-Doku aktualisiert:
|
||
- `routes/ADMIN_ROUTES.md` auf 27 aktive Routen erweitert
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Create-Szenario erweitert:
|
||
- prueft User-Anlage inkl. Rollen, Firmenverknuepfung und optionaler Rechnungsadresse.
|
||
|
||
### Nächster Schritt
|
||
- Optional: User-Detailseite (`admin.users.show`) und dedizierte Kontakt-CRUD-Routen (`admin.contacts.create/edit`) nachziehen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 erweitert: User-Detailseite (`admin.users.show`)
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Nutzeransicht vervollstaendigen mit read-only Detailseite inkl. Firmen-/Kontakt- und Billing-Sicht.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Volt-Komponente `resources/views/livewire/admin/users/show.blade.php` erstellt:
|
||
- zeigt Basisdaten, Rollen, letzten Login, Rechnungsadresse
|
||
- listet verknuepfte Firmen inkl. Pivot-Rolle
|
||
- zeigt Kontakte je verknuepfter Firma
|
||
- Routing erweitert:
|
||
- neue Route `admin.users.show` (`/admin/users/{id}`)
|
||
- User-Index erweitert:
|
||
- Eye-Button auf Detailansicht in `resources/views/livewire/admin/users.blade.php`
|
||
- Routing-Doku aktualisiert:
|
||
- `routes/ADMIN_ROUTES.md` auf 28 aktive Routen erweitert
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Show-Szenario erweitert:
|
||
- prueft Rendern der User-Detailseite inkl. Company- und Billing-Daten.
|
||
|
||
### Nächster Schritt
|
||
- Optional: dedizierte Kontakt-CRUD-Routen (`admin.contacts.create/edit`) aktivieren und auf echte Persistenz umstellen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 erweitert: Kontakte-CRUD (Create/Edit) produktiv
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Kontaktstrecke von Demo-Daten auf echte Persistenz umstellen und fehlende Routen aktivieren.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Volt-Komponente `resources/views/livewire/admin/contacts/create.blade.php`:
|
||
- Anlegen von Kontakten mit Firmenzuordnung, Portal, Anrede, Rollen-/Kontaktdaten
|
||
- optionaler Company-Prefill per Query-Parameter `?company=...`
|
||
- Neue Volt-Komponente `resources/views/livewire/admin/contacts/edit.blade.php`:
|
||
- Bearbeiten bestehender Kontakte auf Basis echter `Contact`-Model-Daten
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` komplett auf produktive Queries umgestellt:
|
||
- Suche/Filter via Eloquent
|
||
- Firmenfilter aus echter `companies`-Tabelle
|
||
- Pagination und Edit-Links auf reale IDs
|
||
- Routing erweitert:
|
||
- `admin.contacts.create` (`/admin/contacts/create`)
|
||
- `admin.contacts.edit` (`/admin/contacts/{id}/edit`)
|
||
- Routing-Doku aktualisiert:
|
||
- `routes/ADMIN_ROUTES.md` auf 30 aktive Routen erweitert
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Kontakt-Create/Edit-Szenario erweitert:
|
||
- prueft Anlage und Bearbeitung von Kontakten ueber die neuen Volt-Formulare.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Companies-Detail/Index enger mit Kontakt-CRUD verknuepfen (z. B. company-spezifische Kontaktansicht als Tab).
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Feinschliff: Company-zu-Kontakt-Flow explizit verdrahtet
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Kontakt-Neuanlage direkt aus Company-Context mit klarer Route + Prefill absichern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue CRM-Route ergaenzt:
|
||
- `admin.companies.contacts.create` (`/admin/companies/{companyId}/contacts/create`)
|
||
- `resources/views/livewire/admin/companies/show.blade.php` angepasst:
|
||
- "Kontakt hinzufuegen"-Aktionen zeigen jetzt auf die neue firmenspezifische Route statt Query-only-Variante.
|
||
- `resources/views/livewire/admin/contacts/create.blade.php` erweitert:
|
||
- `mount(?int $companyId = null)` unterstuetzt jetzt Route-Prefill (plus Fallback auf Query-Param)
|
||
- sichtbarer Hinweis, wenn Firma vorausgewaehlt ist
|
||
- Routing-Doku aktualisiert:
|
||
- `routes/ADMIN_ROUTES.md` auf 31 aktive Routen erweitert
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Prefill-Szenario erweitert:
|
||
- `admin.contacts.create` mit `companyId` setzt `companyId` und `isCompanyPrefilled` korrekt.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Company-Detailseite um eigenen Kontakte-Tab mit Inline-Filterung erweitern.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Feinschliff: Company-Detail mit Kontakte-Tab + Inline-Filter
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Company-Detailseite auf echte Daten umstellen und Kontakte innerhalb der Seite filterbar machen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/companies/show.blade.php` von Dummy-Daten auf echte Eloquent-Queries umgestellt:
|
||
- laedt `contacts`, `pressReleases`, `users` inkl. Counts
|
||
- zeigt reale Firmenstammdaten statt statischer Platzhalter
|
||
- Tabs innerhalb der Company-Detailseite eingefuehrt:
|
||
- `overview`
|
||
- `contacts`
|
||
- Im Kontakte-Tab eine Inline-Suche (`contactSearch`) eingebaut:
|
||
- filtert nach Name, E-Mail und Rolle direkt innerhalb der Detailseite
|
||
- Kontakt-Neuanlage und Kontakt-Bearbeiten bleiben direkt verlinkt
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Filter-Szenario erweitert:
|
||
- prueft, dass im Company-Kontakte-Tab die Inline-Suche Treffer zeigt und Nicht-Treffer ausblendet.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Contacts-Index um Portal-Filter und Quick-Jump zur jeweiligen Company-Detailseite erweitern.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Feinschliff: Contacts-Index mit Portal-Filter und Company-Quick-Jump
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Kontakte-Liste fuer operatives Arbeiten erweitern (Mandantenfilter + schneller Firmenkontext).
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` erweitert:
|
||
- neuer `portalFilter` (`presseecho|businessportal24|both|all`) auf Query-Ebene
|
||
- zusaetzliches Portal-Select in den Filtern
|
||
- Company-Spalte jetzt mit klickbarem Link auf `admin.companies.show`
|
||
- zusaetzlicher Quick-Jump-Button in den Zeilenaktionen zur Firmen-Detailseite
|
||
- Query-Logik bleibt paginiert und kombiniert Such-/Firmen-/Portal-Filter.
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Szenario erweitert:
|
||
- prueft Portal-Filterung im Contacts-Index
|
||
- prueft, dass der Company-Quick-Jump-Link im Render enthalten ist.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Contacts-Index um gespeicherte Filter-Presets (pro User) erweitern.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Feinschliff: Persistente Filter-Presets pro User im Contacts-Index
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Wiederverwendbare Filterkonfigurationen pro User speichern, anwenden und loeschen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- Neue Tabelle `user_filter_presets` eingefuehrt:
|
||
- Migration `2026_04_24_120000_create_user_filter_presets_table.php`
|
||
- Felder: `user_id`, `page`, `name`, `filters` (JSON), inkl. Unique auf `(user_id, page, name)`
|
||
- Neues Model `App\Models\UserFilterPreset` angelegt (JSON-Cast fuer `filters`).
|
||
- `App\Models\User` um Relation `filterPresets()` erweitert.
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` erweitert:
|
||
- Preset speichern (`savePreset`)
|
||
- Preset anwenden (`applyPreset`)
|
||
- Preset loeschen (`deletePreset`)
|
||
- UI-Elemente fuer Preset-Name, Preset-Auswahl und Aktionen integriert
|
||
- gespeicherte Werte: `search`, `companyFilter`, `portalFilter`
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Preset-Szenario erweitert:
|
||
- prueft Speichern, Anwenden und Loeschen eines Contacts-Filter-Presets pro User.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Default-Preset pro User markieren und beim Oeffnen des Contacts-Index automatisch anwenden.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Feinschliff: Default-Preset im Contacts-Index
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Ein Preset pro User/Seite als Standard markieren und beim Seitenaufruf automatisch anwenden.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `user_filter_presets` erweitert:
|
||
- neue Migration `2026_04_24_121500_add_is_default_to_user_filter_presets_table.php`
|
||
- neues Feld `is_default` + Index auf `(user_id, page, is_default)`
|
||
- `App\Models\UserFilterPreset` erweitert:
|
||
- `is_default` in Fillable + Boolean-Cast
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` erweitert:
|
||
- `mount()` laedt und appliziert automatisch das Standard-Preset
|
||
- neue Aktion `setDefaultPreset()` setzt genau ein Preset als Standard pro User/Seite
|
||
- UI um Button **"Als Standard"** erweitert
|
||
- Preset-Liste kennzeichnet den Standard sichtbar mit `(Standard)`
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Default-Preset-Szenario erweitert:
|
||
- prueft Setzen des Standard-Presets
|
||
- prueft Auto-Anwendung beim erneuten Laden des Contacts-Index.
|
||
|
||
### Nächster Schritt
|
||
- Optional: Position/Sortierung der Presets per Drag&Drop oder manuellem Sort-Key ergaenzen.
|
||
|
||
---
|
||
|
||
## 2026-04-24 – P3 Feinschliff: Preset-Sortierung nach letzter Nutzung
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Presets operativ priorisieren (Standard zuerst, danach zuletzt verwendete).
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `user_filter_presets` erweitert:
|
||
- neue Migration `2026_04_24_123000_add_last_used_at_to_user_filter_presets_table.php`
|
||
- neues Feld `last_used_at` + Index auf `(user_id, page, last_used_at)`
|
||
- `App\Models\UserFilterPreset` erweitert:
|
||
- `last_used_at` als Datetime-Cast
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` angepasst:
|
||
- Preset-Liste sortiert jetzt nach:
|
||
1) `is_default` DESC
|
||
2) `last_used_at` DESC
|
||
3) `name` ASC
|
||
- `applyPreset()` aktualisiert `last_used_at`
|
||
- `savePreset()` setzt initiales `last_used_at` auf `now()`
|
||
|
||
### Verifikation
|
||
- `tests/Feature/Admin/UserManagementTest.php` um Szenario erweitert:
|
||
- prueft, dass `applyPreset()` den Timestamp aktualisiert und ein zuvor aelteres Preset nach Nutzung als "zuletzt verwendet" markiert ist.
|
||
|
||
### Nächster Schritt
|
||
- Optional: manuelle Preset-Reihenfolge (Sort-Key) als erweitertes UX-Feature einbauen.
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Pressemitteilungen: User-, Firmen- und Kontakt-Livefilter
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Aus dem User-Detailmodal direkt in die PM-Liste springen und dort nach User/Firma/Kontakt per Live-Suche filtern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/users.blade.php` erweitert:
|
||
- Detailmodal zeigt bei Pressemitteilungen einen Direktlink zu den veroeffentlichten PMs des Users.
|
||
- Link setzt die PM-Filter per Query-String (`user`, `status=published`).
|
||
- `resources/views/livewire/admin/press-releases/index.blade.php` erweitert:
|
||
- URL-Filter fuer `status`, `user`, `company`, `contact`.
|
||
- Livewire/Flux-Comboboxen fuer User-, Firmen- und Kontakt-Suche.
|
||
- Filter greifen performant auf `user_id`, `company_id` und die PM-Kontakt-Relation.
|
||
- Feinschliff: obere Filter als responsive Grid-Zeile, Button **Neue PM** aus der Filter-Card herausgezogen.
|
||
- Live-Suchen laden initial keine Optionsdaten mehr; Ergebnisse werden erst nach Suchbegriff oder bestehender Auswahl geladen.
|
||
- Aktive User-/Firmen-/Kontaktfilter koennen per kleinem X wieder zurueckgesetzt werden.
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- prueft den Direktlink aus dem User-Detailmodal.
|
||
- prueft User-, Firmen- und Kontaktfilter in der PM-Uebersicht inkl. Live-Suchergebnissen.
|
||
- prueft, dass die Live-Suchergebnisse initial leer bleiben und Reset-Aktionen funktionieren.
|
||
|
||
### Verifikation
|
||
- `vendor/bin/pint --format agent resources/views/livewire/admin/press-releases/index.blade.php resources/views/livewire/admin/users.blade.php tests/Feature/Admin/UserManagementTest.php`
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` -> 22 passed, 169 assertions
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Firmen/Kontakte: PM-Counts und erweiterte Filter
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Firmen- und Kontaktuebersichten mit weiteren Filtern und sichtbaren PM-/Relationszaehlern erweitern.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/contacts/index.blade.php` erweitert:
|
||
- PM-Count pro Kontakt per `withCount('pressReleases')`.
|
||
- Neue Datenstandsfilter: mit/ohne Pressemitteilungen.
|
||
- Neuer User-Livefilter zusaetzlich zum bestehenden Firmen-Livefilter.
|
||
- PM-Count verlinkt direkt zur PM-Liste mit gesetztem `contact`-Filter.
|
||
- Contact-Presets speichern und laden jetzt auch User- und Datenstandsfilter.
|
||
- `resources/views/livewire/admin/companies/index.blade.php` erweitert:
|
||
- Datenstandsfilter fuer mit/ohne Pressemitteilungen und mit/ohne Kontakte.
|
||
- Neue Livefilter fuer User und Kontakte.
|
||
- PM- und Kontakt-Counts bleiben sichtbar und verlinken direkt in die gefilterten PM-/Kontaktlisten.
|
||
- UX-Feinschliff:
|
||
- Alle suchbaren Select-/Combobox-Felder in PM-, Firmen-, Kontakt- und User-Bearbeiten-Flows haben jetzt einen eigenen kleinen X-Button zum Zuruecksetzen der jeweiligen Suche/Auswahl.
|
||
- `tests/Feature/Admin/UserManagementTest.php` erweitert:
|
||
- prueft Kontakt-PM-Counts, Contact-Filter und User-/Firmen-Livefilter.
|
||
- prueft Firmen-PM-/Kontakt-Counts, Datenstandsfilter und User-/Kontakt-Livefilter.
|
||
- prueft die neuen Reset-Aktionen fuer die suchbaren Selectfelder.
|
||
|
||
### Verifikation
|
||
- `vendor/bin/pint --format agent resources/views/livewire/admin/contacts/index.blade.php resources/views/livewire/admin/companies/index.blade.php tests/Feature/Admin/UserManagementTest.php`
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` -> 24 passed, 212 assertions
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Legacy Firmenlogos: Sync vorbereitet
|
||
|
||
**Phase:** P6 (Daten-Migration) / P3 (Admin-UI)
|
||
**Aufgaben:** Aus den abgelegten Legacy-Logoordnern nur die weiterhin referenzierten Firmenlogos in einen sauberen Storage-Pfad übernehmen und mit `companies.logo_path` verknüpfen.
|
||
**Status:** 🔄 vorbereitet, echter Sync noch nicht ausgeführt
|
||
|
||
### Was wurde gemacht
|
||
- Neuer Artisan-Command `legacy:sync-company-logos`:
|
||
- Quelle: `storage/app/public/{portal}/company`
|
||
- Ziel: `storage/app/public/company-logos/{portal}/{company_id}/{dateiname}`
|
||
- aktualisiert `companies.logo_path` auf den neuen relativen Public-Storage-Pfad
|
||
- unterstützt `--portal=presseecho|businessportal24|all`
|
||
- unterstützt `--dry-run` und `--force`
|
||
- arbeitet nicht-destruktiv: Quellordner werden nicht gelöscht oder verschoben
|
||
- meldet fehlende referenzierte Dateien und ungenutzte Dateien pro Portal
|
||
- Tests ergänzt:
|
||
- erfolgreicher Kopier-/DB-Verknüpfungsfall
|
||
- Dry-Run verändert keine Dateien und keine DB-Werte
|
||
- fehlende referenzierte Dateien führen zu einem fehlgeschlagenen Command
|
||
|
||
### Dry-Run gegen lokale Logoordner
|
||
- `presseecho`: 951 referenziert, 951 kopierbar, 0 fehlend, 0 ungenutzt
|
||
- `businessportal24`: 3.855 referenziert, 3.849 kopierbar, 6 fehlend, 53 ungenutzt
|
||
- Gesamt: 4.806 referenziert, 4.800 kopierbar, 6 fehlend, 53 ungenutzt
|
||
|
||
### Verifikation
|
||
- `vendor/bin/pint --format agent app/Console/Commands/SyncCompanyLogos.php tests/Feature/SyncCompanyLogosTest.php`
|
||
- `php artisan test --compact tests/Feature/SyncCompanyLogosTest.php` -> 3 passed, 11 assertions
|
||
- `php artisan legacy:sync-company-logos --dry-run` -> erwartungsgemäß fehlgeschlagen wegen 6 fehlender BP24-Dateien
|
||
|
||
### Nächster Schritt
|
||
- Entscheiden, ob die 6 fehlenden BP24-Logos nachgeliefert werden oder ob wir diese Firmen ohne Logo übernehmen.
|
||
- Danach echten Sync ausführen: `php artisan legacy:sync-company-logos`
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Firmen/Kontakte: Portalzuordnung sichtbar
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Migrierte Firmen und Kontakte muessen ihre Portalzuordnung in Listen und Detail-/Bearbeitungsansichten sichtbar zeigen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `resources/views/livewire/admin/companies/index.blade.php`:
|
||
- neue Tabellenspalte **Portal** mit farbigem Flux-Badge.
|
||
- `resources/views/livewire/admin/companies/show.blade.php`:
|
||
- Detailheader nutzt jetzt das Portal-Label statt technischem Uppercase-Wert.
|
||
- Kontakte im Firmen-Detailtab zeigen ebenfalls ein Portal-Badge.
|
||
- `resources/views/livewire/admin/contacts/index.blade.php`:
|
||
- neue Tabellenspalte **Portal** mit farbigem Flux-Badge.
|
||
- `resources/views/livewire/admin/contacts/edit.blade.php`:
|
||
- Bearbeitungskopf zeigt die aktuelle Portalzuordnung als Badge.
|
||
- `tests/Feature/Admin/UserManagementTest.php`:
|
||
- Assertions fuer Portalspalten/-badges in Firmen- und Kontaktuebersicht.
|
||
- Assertions fuer Portal-Badge in Firmen-Detail und Kontakt-Bearbeitung.
|
||
|
||
### Verifikation
|
||
- `vendor/bin/pint --format agent resources/views/livewire/admin/companies/index.blade.php resources/views/livewire/admin/companies/show.blade.php resources/views/livewire/admin/contacts/index.blade.php resources/views/livewire/admin/contacts/edit.blade.php tests/Feature/Admin/UserManagementTest.php`
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` -> 24 passed, 219 assertions
|
||
|
||
---
|
||
|
||
## 2026-04-30 – Admin Firmen: Portalfilter und Logo-Vorschau
|
||
|
||
**Phase:** P3 (Admin-UI an Models binden)
|
||
**Aufgaben:** Firmen nach Portal filterbar machen und Firmenlogos in Liste, Detailansicht und Bearbeitung sichtbar anzeigen.
|
||
**Status:** ✅
|
||
|
||
### Was wurde gemacht
|
||
- `app/Models/Company.php`:
|
||
- neue zentrale Methode `logoUrl()` fuer Public-Storage-URLs.
|
||
- unterstuetzt bereits synchronisierte Pfade (`company-logos/...`) und Legacy-Logoordner (`storage/app/public/{portal}/company/...`).
|
||
- `resources/views/livewire/admin/companies/index.blade.php`:
|
||
- neuer URL-synchronisierter Portalfilter (`?portal=...`).
|
||
- Logo-Miniatur in der Firmenzeile mit Platzhalter, wenn kein Logo aufloesbar ist.
|
||
- `resources/views/livewire/admin/companies/show.blade.php`:
|
||
- Detailheader nutzt ebenfalls `logoUrl()` statt rohem DB-Pfad.
|
||
- `resources/views/livewire/admin/companies/edit.blade.php`:
|
||
- aktuelle Logo-Vorschau im Bearbeiten-Flow nutzt dieselbe zentrale URL-Logik.
|
||
- `tests/Feature/Admin/UserManagementTest.php`:
|
||
- Assertions fuer Portalfilter und Legacy-Logo-Vorschau in Liste, Ansicht und Bearbeitung.
|
||
|
||
### Verifikation
|
||
- `php vendor/bin/pint --format agent app/Models/Company.php resources/views/livewire/admin/companies/index.blade.php resources/views/livewire/admin/companies/show.blade.php resources/views/livewire/admin/companies/edit.blade.php tests/Feature/Admin/UserManagementTest.php`
|
||
- `php artisan test --compact tests/Feature/Admin/UserManagementTest.php` -> 24 passed, 225 assertions
|
||
|
||
---
|