presseportale/dev/migration 2026/MIGRATION-STEPS.md
Kevin Adametz 5a9aab7012 Migration: PM-Datum korrekt übernehmen, UUID stabil, Zwei-Phasen-Runbook
- published_at = Legacy-created_at (Publikationsdatum, Frontend-Sortier-
  schlüssel) statt updated_at, das bei Alt-Daten oft den Massen-/Migrations-
  stempel trägt und ein falsches "frisches" Datum erzeugte.
- created_at/updated_at per forceFill direkt im Import gesetzt (waren nicht
  fillable, wurden still verworfen) – Import allein ist jetzt datumssauber.
- legacy:fix-timestamps korrigiert zusätzlich published_at (status=published).
- PM-UUID bleibt beim Re-Import/Delta-Lauf erhalten (kein Neu-Würfeln).
- MIGRATION-STEPS auf Zwei-Phasen-Strategie umgestellt: migrate, Phase 1 mit
  --force, Phase 2 (Delta) ohne --force, Grandfather-Re-Run, Idempotenz-Tabelle.
- Tests: LegacyPressReleaseDateImportTest (published_at-Quelle, Entwurf,
  Force-Re-Import erhält UUID + Datum).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:32:48 +00:00

127 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Migration Steps aktuelles Runbook
Stand: 2026-06-17. Spiegelt den aktuell implementierten Command-Stand. Details und Go-Live-Kontext stehen in `05-DATABASE-MERGE.md` und `08-PROGRESS.md`; die Live-Settings/Commands rund um den Go-Live in `../../docs/weiteres/Live-Deployment-Checkliste (WS-7).md`.
## Zwei-Phasen-Strategie (kurzer Ausfall)
Der Import ist **wiederholbar**. Vorgesehener Ablauf:
1. **Phase 1 Voll-Import (jetzt):** Neues System auf `pressekonto.com` aufsetzen, Schema migrieren, kompletten Bestand mit `--force` importieren, Frontends auf Test-Domain verifizieren.
2. **Phase 2 Delta-Lauf (beim Cutover):** Altsystem kurz einfrieren, dieselben Commands **ohne `--force`** erneut laufen lassen → bestehende Datensätze bleiben unangetastet, nur neue/zwischenzeitlich entstandene werden nachgezogen. Danach DNS umstellen.
> **Die `--force`-Disziplin ist entscheidend:**
> - **MIT `--force`** (nur Phase 1): überschreibt bestehende Datensätze.
> - **OHNE `--force`** (Phase 2): überspringt bereits importierte Datensätze (`legacy_import_map`), importiert ausschließlich Neue. Genau das Verhalten für einen sauberen Delta-Lauf.
---
## Phase 0 Schema (einmalig, vor dem ersten Import)
```bash
php artisan migrate --force
```
Ohne das Schema schlägt jeder Import-Schritt fehl. Auf dem Live-System ist das Teil der WS-7-Checkliste.
## Dry-Run (vor jedem echten Lauf)
```bash
php artisan legacy:import --source=all --dry-run
php artisan legacy:archive-invoices --dry-run
php artisan legacy:grandfather-subscriptions --dry-run
php artisan legacy:verify --no-report
php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration --dry-run
```
Hinweis: `legacy:archive-invoices` importiert die Legacy-Rechnungen vollständig in `legacy_invoices`, inkl. Status/User-Zuordnung, `raw_snapshot`, `pdf_payload` und Report. Die PDF-Erzeugung erfolgt im Customer-Bereich bei Abruf aus diesen Archivdaten.
---
## Phase 1 Voll-Import (mit `--force`, korrekte Reihenfolge)
```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:grandfather-subscriptions
php artisan legacy:fix-timestamps
php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration
php artisan legacy:verify
```
Reihenfolge ist wegen FKs Pflicht (companies vor contacts vor press-releases; users zuerst). Der Schritt `--step=users` importiert neben `sf_guard_user` auch `sf_guard_user_profile` in die neue Tabelle `profiles`.
### Datums-Migration der Pressemitteilungen (wichtig für die Frontend-Sortierung)
Das Frontend sortiert/clustert nach `published_at`. Der Importer setzt daher:
- `created_at`/`updated_at` = Legacy-`created_at`/`updated_at` (per `forceFill`, da nicht fillable) — das echte Anlage-/Bearbeitungsdatum, **nicht** das Importdatum.
- `published_at` = Legacy-`created_at` (Publikationsdatum) für veröffentlichte Meldungen. Bewusst **nicht** `updated_at` — dort steht bei vielen Alt-Datensätzen der Massen-/Migrationsstempel (z. B. `2026-04-…`), der sonst als „frisches" Veröffentlichungsdatum erschiene.
`legacy:fix-timestamps` ist der schnelle Cross-DB-Resync (gleicher MySQL-Server) und korrigiert `created_at`, `updated_at` **und** `published_at` (für `status=published`) authoritativ. Bei einem reinen Delta-Lauf ohne Re-Import diesen Schritt mitlaufen lassen.
---
## Phase 2 Delta-Lauf beim Cutover (OHNE `--force`)
Altsystem einfrieren, dann:
```bash
# Vorab sehen, was neu dazukommt
php artisan legacy:import --source=all --dry-run
# Nur neue Datensätze nachziehen (kein --force!)
php artisan legacy:import --source=all
php artisan legacy:archive-invoices
php artisan legacy:grandfather-subscriptions # replay-fähig: aktualisiert bestehende, ergänzt neue
php artisan legacy:fix-timestamps # korrigiert created_at/updated_at/published_at
php artisan legacy:verify
# Medien NICHT mit --force würde im neuen System gepflegte Logos/Bilder überschreiben.
# Nur falls neue Medien fehlen, gezielt ohne --force nachladen:
# php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration
```
Danach DNS auf das neue System umstellen. `legacy:grandfather-subscriptions` läuft **nach** `legacy:archive-invoices`, weil es die aktiven, jährlich wiederkehrenden Zahlungsvereinbarungen aus dem Rechnungsarchiv ableitet (jüngste Rechnung pro Vereinbarung mit `payment_option.type = recurring` und `user_payment_option.status = active`) und als `grandfathered` in `user_payment_options` schreibt (Beträge netto in `legacy_conditions.net_cents`). Die nächste Rechnung stellt danach der tägliche MAN-Kreis-Lauf (`billing:generate-manual-invoices`) zum gewohnten Rhythmus aus. Optionen: `--dry-run`, `--as-of=`, `--grace-months=12` (älter überfällige Vereinbarungen gelten als stale und bleiben reines Archiv).
---
## Idempotenz / Replay-Sicherheit (Kurzreferenz)
Alle Schritte schreiben per `updateOrCreate`/`insertOrIgnore` über `(legacy_portal, legacy_id)` bzw. `legacy_import_map`; **kein Truncate, kein Delete**. Re-Run-Verhalten:
| Schritt | ohne `--force` | mit `--force` | zieht Neue nach |
|---|---|---|---|
| categories / users / companies / contacts | SKIP | UPDATE | ja |
| press-releases | SKIP | UPDATE (uuid + slug bleiben erhalten) | ja |
| link-associations | `insertOrIgnore` (idempotent) | — | ja |
| archive-invoices | SKIP | UPDATE | ja |
| grandfather-subscriptions | UPDATE/INSERT je Vereinbarung | — | ja |
| fix-timestamps | Raw-UPDATE (idempotent) | — | ja |
| migrate-media | SKIP (Pfad-Check) | **OVERWRITE** (Vorsicht) | ja |
Vor Phase 2 immer `legacy:verify` laufen lassen, um Lücken aus Phase 1 (z. B. fehlgeschlagene User-Importe → verwaiste Rechnungen) zu erkennen.
---
## Noch nicht finalisiert
- Medien-/Bilddateien-Transfer: Scope/Command vorhanden (`legacy:migrate-media`), finale Base-Path-Strategie auf Live noch festzulegen.
- Staging-Rehearsal mit aktuellem Produktiv-Snapshot bleibt Pflicht vor Go-Live.
## Legacy-Rechnungsreport
`legacy:archive-invoices` schreibt standardmäßig:
```bash
storage/app/private/migration/legacy-invoices-*.json
```
Der Report enthält pro Portal: Source-Count, importierte/übersprungene/fehlerhafte Rechnungen, Summe in Cent, Statusverteilung, Anzahl unzugeordneter Legacy-User, Anzahl erzeugter PDF-Payloads.