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

6.8 KiB
Raw Blame History

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)

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)

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)

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:

# 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:

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.