P6.6: legacy:grandfather-subscriptions — aktive Legacy-Abos aus dem Rechnungsarchiv migrieren

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>
This commit is contained in:
Kevin Adametz 2026-06-12 10:35:48 +00:00
parent d548f4b235
commit 1cd4d8e33a
8 changed files with 526 additions and 12 deletions

View file

@ -214,12 +214,20 @@ Vor dem Go-Live-Rehearsal muss der Report gegen den aktuellen Produktiv-Snapshot
### 5.6 Payment ⭐ NEU + GRANDFATHERING (D-13)
- **Legacy-PaymentOptions werden nicht übernommen.** Neue Produkte werden vom Auftraggeber definiert und als Stripe-Prices angelegt.
- **Aktive `UserPaymentOption`-Einträge** (Status `active`, `valid_until >= today`) werden als `grandfathered` migriert:
- Neuer Datensatz in `user_payment_options` mit `status = 'grandfathered'`, `grandfathered_until = legacy.valid_until`, `legacy_conditions = {...Snapshot...}`.
- **Kein** Stripe-Subscription-Versuch (kein automatischer Import alter Abos in Stripe).
- Scheduler `ExpireGrandfatheredSubscriptions` erzeugt am `grandfathered_until` eine Customer-Benachrichtigung für Umstellung auf neues Produkt.
> **Umgesetzt 2026-06-12** mit präzisierten Kriterien des Auftraggebers:
> Quelle der Aktiv-Erkennung ist **ausschließlich das Rechnungsarchiv**
> (`legacy_invoices`, D-12) — nicht die Legacy-Payment-Tabellen direkt.
> Command: `legacy:grandfather-subscriptions` (idempotent, Replay-fähig).
- **Legacy-PaymentOptions werden nicht übernommen.** Neue Produkte werden vom Auftraggeber definiert und als Stripe-Prices angelegt. Für die Grandfathered-Vereinbarungen entstehen versteckte Katalog-Platzhalter (`payment_options.article_number = LEGACY-{PE|BP}-{Artikel}`, `is_hidden = true`); die verbindlichen Beträge liegen pro Vereinbarung in `legacy_conditions`.
- **Aktiv-Regel** (aus dem Archiv abgeleitet): jüngste Rechnung pro (Portal, Legacy-`user_payment_option`) mit `pdf_payload.payment_option.type = 'recurring'` und `pdf_payload.user_payment_option.status = 'active'`; `next_due_date` darf höchstens `--grace-months` (Default 12) überfällig sein, sonst gilt die Vereinbarung als stale und bleibt reines Archiv. Einmal-Käufe (`type = single`) werden nie übernommen.
- **Übernahme** als `grandfathered` in `user_payment_options`:
- `status = 'grandfathered'`, `grandfathered_until = legacy.valid_until_date` (nullable), `stripe_subscription_id = null`.
- `current_period_start = service_period_begin_date` der jüngsten Rechnung, `current_period_end = next_due_date` → der tägliche MAN-Kreis-Lauf (`billing:generate-manual-invoices`) stellt die nächste Rechnung zum gewohnten (jährlichen) Rhythmus aus, mit den Beträgen der letzten Legacy-Rechnung (`legacy_conditions.amount/tax/total_cents`).
- **Kein** Stripe-Subscription-Versuch (kein automatischer Import alter Abos in Stripe). Neue manuelle Rechnungen entstehen im **MAN-Rechnungskreis** (`invoices`), nie im Archiv.
- Scheduler `ExpireGrandfatheredSubscriptions` (Customer-Benachrichtigung am `grandfathered_until`) bleibt offen — folgt mit dem Stripe-Billing-Block.
- Alle historischen `user_payments` werden als Information ins Archiv geschrieben (analog `legacy_invoices` optional).
- **Replay (D-18)**: Re-Runs aktualisieren bestehende Einträge anhand `legacy_conditions.legacy_portal` + `legacy_user_payment_option_id` — der Lauf kurz vor dem Relaunch übernimmt damit den dann aktuellen Stand ohne Duplikate.
### 5.7 Coupons

View file

@ -4,6 +4,34 @@ Chronologisches Protokoll aller Migrationsschritte. Jede Session / jeder Commit
---
## 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

View file

@ -107,7 +107,7 @@ Der Kern (Erstellen → Submit → Review → Publish/Reject mit Reason + Audit-
| # | Aufgabe | Priorität | Status |
|---|---|---|---|
| 6.5d | **Legacy-Rechnungen Vollimport**: alle bestehenden Rechnungen aus den Legacy-DBs inkl. Status, Beträgen, Datum, Zahlart, vollständigem Raw-Snapshot und User-Zuordnung importieren; `legacy:archive-invoices` schreibt Import-Report + PDF-Payload; PDF-Erzeugung bleibt DB-basiert/on-demand statt Datei-Migration | 🔴 | ✅ umgesetzt 2026-05-04; Rehearsal gegen Produktiv-Snapshot bleibt P6.10 |
| 6.6 | `legacy:grandfather-subscriptions` (aktive Alt-Abos übernehmen) | 🔴 | ⬜ wartet auf Auftraggeber-Kriterien |
| 6.6 | `legacy:grandfather-subscriptions` (aktive Alt-Abos übernehmen) | 🔴 | ✅ umgesetzt 2026-06-12 (Kriterien vom Auftraggeber: Quelle ist das Rechnungsarchiv — jüngste Rechnung pro Vereinbarung mit `recurring` + `active`; Übernahme als `grandfathered` mit `current_period_end = next_due_date`, MAN-Kreis fakturiert weiter; Replay-fähig, Rehearsal bleibt P6.10) |
| 6.10 | **Rehearsal-Lauf** gegen produktiven Snapshot auf Staging | 🔴 | ⬜ |
**Wichtig für 6.5d:** `legacy:archive-invoices` importiert jetzt Rechnungsdaten, Billing-Adress-Snapshot und User-Payment-Snapshot in `legacy_invoices.raw_snapshot`/`pdf_payload`, zählt unzugeordnete Legacy-User im Report und lässt diese Rechnungen trotzdem im Archiv. Für Legacy-Rechnungen bleibt die bestehende Logik erhalten: Rechnung liegt als Datenbankdatensatz vor und das PDF wird bei Bedarf auf Knopfdruck aus diesen Daten erzeugt. Neue Stripe-Rechnungen werden separat in P8 geplant. Der finale Nachweis der Vollständigkeit erfolgt weiterhin im Staging-Rehearsal mit aktuellem Produktiv-Snapshot.

View file

@ -1,12 +1,13 @@
# Migration Steps aktuelles Runbook
Stand: 2026-05-04. Dieses Kurz-Runbook spiegelt den aktuell implementierten Command-Stand. Details und Go-Live-Kontext stehen in `05-DATABASE-MERGE.md` und `08-PROGRESS.md`.
Stand: 2026-06-12. Dieses Kurz-Runbook spiegelt den aktuell implementierten Command-Stand. Details und Go-Live-Kontext stehen in `05-DATABASE-MERGE.md` und `08-PROGRESS.md`.
## Dry-Run
```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
@ -27,10 +28,22 @@ 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:verify
```
Hinweis: `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. Die nächste
Rechnung stellt danach der tägliche MAN-Kreis-Lauf
(`billing:generate-manual-invoices`) zum gewohnten Rhythmus aus. Re-Runs
aktualisieren bestehende Einträge (Replay-fähig für den Lauf kurz vor dem
Relaunch). Optionen: `--dry-run`, `--as-of=`, `--grace-months=12` (älter
überfällige Vereinbarungen gelten als stale und bleiben reines Archiv).
Hinweis: Der Schritt `--step=users` importiert nicht nur `sf_guard_user`, sondern auch die direkt verknüpften Daten aus `sf_guard_user_profile` in die neue Tabelle `profiles`.
## Alternativer Komplettlauf
@ -38,6 +51,7 @@ Hinweis: Der Schritt `--step=users` importiert nicht nur `sf_guard_user`, sonder
```bash
php artisan legacy:import --source=all --force
php artisan legacy:archive-invoices
php artisan legacy:grandfather-subscriptions
php artisan legacy:fix-timestamps
php artisan legacy:verify
php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migration
@ -45,7 +59,6 @@ php artisan legacy:migrate-media --portal=all --type=all --base-path=dev/migrati
## Noch nicht im Runbook finalisiert
- `legacy:grandfather-subscriptions`: noch nicht implementiert bzw. blockiert durch Kriterien vom Auftraggeber.
- Medien-/Bilddateien-Transfer: Scope und finaler Command noch offen.
- Staging-Rehearsal mit aktuellem Produktiv-Snapshot bleibt Pflicht vor Go-Live.