# M3 SaaS-Basis Vorbereitung

Stand: 2026-07-13

M3 führt Mandanten, Benutzer, Mitgliedschaften und Grundeinstellungen additiv
ein. Die bestehende Legacy-App bleibt dabei lauffähig und wird noch nicht auf
das neue Modell umgestellt.

Status: abgeschlossen für den M3-Scope. M4 startet mit der additiven
Migration von Einzahlungen und Kaffeeverbrauch nach `ledger_entries`.

## Ziel

- Einen Default-Tenant für den aktuellen Bestand erzeugen.
- Login-Benutzer und Kaffee-Teilnehmer fachlich trennen.
- Rollen tenant-spezifisch vorbereiten.
- Bestehende `kl_Mitarbeiter` ohne Datenverlust in `participants` spiegeln.
- Die Grundlage für Registrierung und Login schaffen, ohne die Legacy-Auth
  sofort zu ersetzen.

## Nicht-Ziele für den ersten M3-Schritt

- Kein kompletter Login-Umbau in einem Schritt.
- Keine Migration von Einzahlungen und Kaffeeverbrauch nach `ledger_entries`;
  das gehört zu M4.
- Kein Design- oder Layout-Umbau.
- Kein Billing und keine Tarife im ersten Schritt.
- Kein produktiver Tenant-Wechsel in bestehenden Legacy-Seiten.

## Umgesetzter erster Schritt

Umgesetzte Migrationen:

```text
database/migrations/0002_saas_identity_tenants.sql
database/migrations/0003_saas_auth_account_fields.sql
database/migrations/0004_saas_auth_tokens.sql
database/migrations/0005_saas_tenant_resolution.sql
```

Tabellen:

- `tenants`
- `tenant_settings`
- `users`
- `tenant_memberships`
- `participants`
- `tenant_domains`

Ergänzende Skripte:

```text
scripts/backfill-default-tenant.php
scripts/check-m3-saas-basis.php
scripts/check-m3-auth-flow.php
scripts/check-m3-settings-flow.php
scripts/check-m3-password-email-flow.php
scripts/check-m3-tenant-resolution-flow.php
scripts/check-m3-mail-flow.php
```

Wichtige Regeln:

- `users` sind Login-Konten.
- `participants` sind Kaffeelisten-Teilnehmer.
- Ein `participant` kann optional auf einen `user` zeigen.
- Rollen liegen in `tenant_memberships`, nicht in `participants`.
- `participants.legacy_mitarbeiter_id` speichert die alte ID aus
  `kl_Mitarbeiter`.
- Geldwerte in neuen Settings als Cent-Integer speichern.
- Die Migration ist rein additiv; bestehende Legacy-Seiten fragen weiter die
  bisherigen `kl_*`-Tabellen ab.

## Default-Tenant

Für den Bestand wird ein erster Tenant angelegt:

```text
slug: default
name: Kaffeeliste Bestand
status: active
timezone: Europe/Berlin
locale: de-DE
currency_code: EUR
```

Die Werte können später in einer Tenant-Einstellungsseite angepasst werden.

## Backfill

Ein separates Skript spiegelt die bestehenden Mitarbeiter:

```text
scripts/backfill-default-tenant.php
```

Vorgang:

1. Default-Tenant anlegen oder laden.
2. `tenant_settings` aus `kl_config` erzeugen.
3. Für jede Zeile aus `kl_Mitarbeiter` einen `participant` anlegen oder
   aktualisieren.
4. Für aktive Admins optional einen `user` und eine `tenant_membership`
   erzeugen.
5. `admin = 1` als Rolle `admin` abbilden; ein konfigurierter erster Benutzer
   kann Rolle `owner` erhalten.
6. Verwaiste `participants` mit nicht mehr vorhandener `legacy_mitarbeiter_id`
   aus dem Default-Tenant entfernen.

Der Backfill muss idempotent sein und darf keine bestehenden Legacy-Daten
löschen. Gelöscht werden nur SaaS-Spiegelungen, deren Legacy-Mitarbeiter in
`kl_Mitarbeiter` nicht mehr existiert.

Ausgeführter Dev-Stand:

- Default-Tenant `default` wurde angelegt.
- 9 Legacy-Mitarbeiter wurden als `participants` gespiegelt.
- 2 aktive Admin-/Owner-Logins wurden als `users` und
  `tenant_memberships` angelegt.
- Ein zweiter Backfill-Lauf blieb idempotent.

## Registrierung und Login

Umgesetzte Dateien:

```text
app/database.php
app/saas-auth.php
app/saas-mail.php
login.php
register.php
konto.php
logout.php
mandant-auswahl.php
passwort-vergessen.php
passwort-zuruecksetzen.php
email-verifikation-senden.php
email-verifizieren.php
landing.php
assets/images/landing-hero.png
assets/css/public.css
```

Umgesetzter Umfang:

- Registrierung legt Tenant, Owner-User, Default-Settings, Membership und
  Owner-Participant in einer Transaktion an.
- Login prüft `users.password_hash`, aktive Membership und aktiven Tenant.
- Hat ein User genau einen aktiven Mandanten, wird dieser direkt in die Session
  gelegt.
- Hat ein User mehrere aktive Mandanten, führt der Login zur
  Mandantenauswahl.
- Logout beendet die neue PHP-Login-Session.
- `konto.php` zeigt den aktuellen SaaS-Kontext für den angemeldeten User.
- `functions.php` akzeptiert eine SaaS-Login-Session als erste Identitätsquelle,
  lässt `DEV_AUTH_EMAIL` und `AUTH_USER` aber als Legacy-Fallback bestehen.
- `footer.php` bleibt App-Sidebar und zeigt keine Public-Login- oder
  Registrierungslinks mehr.
- Login, Registrierung, Passwort-Reset und E-Mail-Verifikation nutzen das
  Public-Layout im Stil der Landingpage.
- Passwort-Reset erzeugt Single-Use-Tokens und speichert nur Token-Hashes.
- E-Mail-Verifikation erzeugt Single-Use-Tokens und setzt
  `users.email_verified_at`.
- Reset- und Verifikationslinks werden über `app/saas-mail.php` versendet.
  Der Dev-Standard schreibt Mails nach `var/mail`; produktiv kann auf PHP
  `mail()` umgestellt werden.
- Dev-Links werden weiterhin nur im Dev-Modus angezeigt, damit lokale Checks
  ohne echten Postausgang reproduzierbar bleiben.

Noch offen im M3-Auth-Scope:

- Weitergehende Rollenmatrix für spätere SaaS-Seiten.
- Produktive SMTP-/Provider-Anbindung inklusive Bounce-/Fehlerprotokoll.

## Public-Landingpage

Umgesetzte Dateien:

```text
landing.php
assets/images/landing-hero.png
assets/css/public.css
```

Umgesetzter Umfang:

- Öffentliche Landingpage ohne Legacy-DB-Zugriff.
- Hero mit generiertem Bild-Asset, bestehender Typografie und grünem Akzent.
- CTA zu Login und Registrierung.
- Login, Registrierung und öffentliche Auth-Hilfsseiten sind optisch in den
  Public-Bereich integriert.
- Kurzabschnitt zu Stricherfassung, Mandantenfähigkeit und Webspace-Betrieb.
- Die geschützte App-Sidebar bleibt von der Public-Seite getrennt.

## Tenant-Auflösung

Entscheidung für den Webspace-Betrieb:

- Keine Wildcard-Subdomains im ersten Schritt.
- Primäre App-Adresse ist eine zentrale App-Domain wie `app.kaffeeliste.de`.
- Die Public-Seite kann über `kaffeeliste.de` beziehungsweise
  `www.kaffeeliste.de` laufen.
- Der aktive Mandant wird nach Login über `tenant_memberships` und die PHP-
  Session gesetzt.
- Bei genau einem Mandanten wird automatisch weitergeleitet.
- Bei mehreren Mandanten nutzt der User `mandant-auswahl.php`.
- `tenant_domains` ist vorbereitet für später gezielt eingerichtete feste
  Domains oder Subdomains, aber nicht Voraussetzung für den Start.

Noch offen:

- `APP_PRIMARY_HOST` in der Zielumgebung setzen.
- Optional `APP_PUBLIC_HOST` beziehungsweise Host-Rewrite für die Landingpage
  in der Zielumgebung definieren.
- Produktive Domain-/Zertifikatsprüfung für einzelne feste Domains
  definieren.

## Rollen und Grundeinstellungen

Umgesetzte Dateien:

```text
mandant-einstellungen.php
```

Umgesetzter Umfang:

- `saas_user_has_role()`, `saas_require_role()` und
  `saas_can_manage_tenant_settings()` zentralisieren die erste Rollenprüfung.
- Owner und Admin dürfen Tenant-Grundeinstellungen bearbeiten.
- Die Seite bearbeitet Kundenname, Zeitzone, Locale, Währung, Preis pro
  Strich, Web-Striche, PayPal-Link, Listenfenster und Schulden-Warnschwelle.
- Geldwerte werden in der UI als Euro-Werte erfasst und weiterhin als Cent-
  Integer gespeichert.
- `konto.php` und die Sidebar verlinken die Einstellungen nur für passende
  Rollen.

## Erste Akzeptanzkriterien

- Migrationen laufen mehrfach ohne Fehler: erfüllt.
- Default-Tenant existiert genau einmal: erfüllt.
- `tenant_settings` enthalten Preis pro Strich und bestehende PayPal-Optionen:
  erfüllt.
- Jeder bestehende `kl_Mitarbeiter` hat genau einen `participant`: erfüllt.
- `participants.legacy_mitarbeiter_id` ist gesetzt: erfüllt.
- Admins werden als tenant-scoped Rolle abgebildet: erfüllt.
- Registrierung und Login funktionieren für einen neuen Test-Tenant:
  erfüllt.
- Owner-/Admin-Grundeinstellungen können aktualisiert werden: erfüllt.
- Passwort-Reset und E-Mail-Verifikation funktionieren mit Single-Use-Tokens:
  erfüllt.
- Reset- und Verifikationslinks werden im Dev-/Testmodus als Mail-Log erzeugt:
  erfüllt.
- Mandantenauswahl funktioniert für User mit mehreren Mandanten: erfüllt.
- Öffentliche Landingpage ist per HTTP-Smoke erreichbar: erfüllt.
- Golden-Master und HTTP-Smoke bleiben grün: erfüllt.

## Risiken

| Risiko | Gegenmaßnahme |
| --- | --- |
| Login-User und Kaffee-Teilnehmer werden vermischt | `users` und `participants` strikt getrennt halten |
| Mehrfacher Backfill erzeugt Duplikate | Eindeutige Constraints und Upsert-Logik |
| Legacy-App bricht durch neue Tabellen | M3 nur additiv, keine Legacy-Queries umstellen |
| Rollen werden global statt tenant-scoped | Rollen ausschließlich in `tenant_memberships` speichern |
| Falsche Owner-Zuordnung | Owner per Env-Konfiguration oder manuell dokumentierter Entscheidung setzen |

## Empfohlene Reihenfolge

1. Migration `0002_saas_identity_tenants.sql` erstellen: erledigt.
2. Migration `0003_saas_auth_account_fields.sql` erstellen: erledigt.
3. `scripts/backfill-default-tenant.php` erstellen: erledigt.
4. Backfill gegen die Dev-Datenbank ausführen: erledigt.
5. Kontrollskript für Tenant/Participant/Role-Counts schreiben: erledigt.
6. Login-/Registrierungsrouten bauen: erledigt.
7. Auth-Flow-Kontrollskript schreiben: erledigt.
8. Zentrale Rollenprüfung und Mandant-Einstellungen bauen: erledigt.
9. Settings-Flow-Kontrollskript schreiben: erledigt.
10. Migration `0004_saas_auth_tokens.sql` erstellen: erledigt.
11. Passwort-Reset und E-Mail-Verifikation bauen: erledigt.
12. Password-/E-Mail-Flow-Kontrollskript schreiben: erledigt.
13. Migration `0005_saas_tenant_resolution.sql` erstellen: erledigt.
14. Mandantenauswahl und zentrale Session-Auflösung bauen: erledigt.
15. Tenant-Resolution-Kontrollskript schreiben: erledigt.
16. Golden-Master und HTTP-Smoke ausführen: erledigt.
17. Mail-Transport-Abstraktion und Mail-Flow-Kontrollskript bauen: erledigt.
18. Public-Landingpage mit erstem Hero-Asset vorbereiten: erledigt.
19. M3-Abschlusscheck dokumentieren und M4-Datenmigration starten: erledigt.

## Übergabe an M4

M4 ist in `docs/m4-data-migration.md` dokumentiert. Der erste M4-Schritt ist
bewusst additiv:

- `ledger_entries` wird als neue tenant-sichere Buchungstabelle angelegt.
- Legacy-Einzahlungen und Legacy-Kaffeeverbrauch werden gespiegelt.
- Bestehende Seiten bleiben vorerst auf den `kl_*`-Tabellen.
- Summen werden gegen den Golden Master verglichen.
