Files
kaffeekasse-saas/docs/saas-umstrukturierungsplan.md
T
clemensandClaude Sonnet 5 3fc65812ef M8: Backup/Restore-Prozess und Monitoring dokumentiert
docs/betrieb-backup-monitoring.md: taeglicher mysqldump-Cron-Job mit
Aufbewahrung, Restore-Befehl inklusive Hinweis auf scripts/migrate.php,
vierteljaehrlicher Restore-Test. Produktive PHP-Fehlerkonfiguration
(display_errors aus, log_errors an) und die aktiv zu beobachtenden
Signale ohne dediziertes APM-Tool: audit_log fuer ungewoehnliche
Admin-Aktionen, rate_limit_attempts fuer Brute-Force-Versuche,
outbound_emails.status=failed fuer Mailversand-Probleme.

Damit ist M8 fuer den geplanten Scope abgeschlossen, mit einer bewusst
offenen Ausnahme (Content-Security-Policy, braucht Template-Bereinigung
der bestehenden Inline-Styles).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 20:19:20 +02:00

30 KiB

SaaS-Umstrukturierungsplan Kaffeeliste

Stand: 2026-07-13

Dieses Dokument beschreibt den geplanten Umbau der bestehenden Kaffeelisten-App zu einer mehrkundenfähigen SaaS-Anwendung mit öffentlicher Landingpage, Kundenregistrierung und geschützter App. Die bestehende Bedienlogik und das visuelle Grunddesign sollen bewusst erhalten bleiben.

Zielbild

  • Mehrere Kunden können eigene Kaffeelisten betreiben.
  • Jeder Kunde hat isolierte Daten, Einstellungen, Mitglieder und Buchungen.
  • Neue Kunden können sich über eine öffentliche Landingpage registrieren.
  • Die operative App bleibt optisch nah am aktuellen Bestand: Sidebar, Tabellen, schlichte Formulare, HTML5-UP-Anmutung.
  • Authentifizierung, Rollen, Mandantenkontext und Sicherheitsprüfungen werden zentralisiert.
  • Finanznahe Vorgänge werden nachvollziehbar und revisionsfreundlich gespeichert.

Nicht-Ziele für den ersten Umbau

  • Kein kompletter Design-Relaunch.
  • Keine verspielte Marketing-App statt der bestehenden Arbeitsoberfläche.
  • Kein gleichzeitiger Neubau aller Randmodule, wenn diese für den MVP nicht benötigt werden.
  • Kein Dual-Write zwischen Legacy und neuer App als Standardbetrieb.
  • Keine Mandanten-ID aus Formularfeldern oder URL-Parametern als alleinige Sicherheitsbasis.

Ist-Zustand

Die aktuelle App ist eine flache PHP/sqlsrv-Legacy-App im Webroot.

Wichtige Bereiche:

  • index.php: persönliches Dashboard.
  • stricheintragen.php: Sammelerfassung von Kaffee-Strichen.
  • einzahlung.php: Sammelerfassung von Einzahlungen.
  • kaffeeliste.php: Gesamtübersicht.
  • mitarbeiterverwalten.php: Mitglieder- und Adminpflege.
  • letzteneintraege.php: Korrektur beziehungsweise Löschung letzter Buchungen.
  • csvupload.php: Zahlungsimport.
  • exportKaffeeliste.php: PDF-/Listenexport.
  • mailversenden.php: Massenmail.
  • hinweise.php: Hinweise.
  • faq.php: FAQ.

Zentrale technische Beobachtungen:

  • Authentifizierung hängt an Windows/IIS AUTH_USER und LDAP.
  • Die Mailadresse des Nutzers wird in functionsLDAP.php ermittelt.
  • Rollen liegen als Boolean admin direkt auf kl_Mitarbeiter.
  • Datenzugriff findet direkt in den einzelnen PHP-Seiten per sqlsrv_query statt.
  • Es gibt kein sichtbares Migrationssystem und kein DB-Schema im Repository.
  • Fachliche Tabellen haben aktuell keinen Mandantenbezug.
  • Das Design kommt vor allem aus assets/css/main.css und dem HTML5-UP-Layout.
  • Die App-Navigation sitzt faktisch in footer.php; nav.php ist leer.

Grundsatzentscheidung

Der Umbau sollte nicht mit einer rein kosmetischen Landingpage starten, sondern mit einem stabilen SaaS-Fundament:

  1. Bestand dokumentieren und fachliche Ergebnisse einfrieren.
  2. Mandantenmodell, Auth und Rollen sauber aufbauen.
  3. Bestehende Fachlogik schrittweise tenant-sicher übernehmen.
  4. Landingpage und App-Shell trennen, ohne das App-Design neu zu erfinden.

So bleibt die vertraute Kaffeelisten-Oberfläche erhalten, während die Datenbasis und Sicherheit SaaS-fähig werden.

Zielarchitektur

Public-Bereich

Der Public-Bereich ist ohne Login erreichbar und lädt keine Mandantendaten.

Empfohlene Routen:

  • /: Landingpage.
  • /preise oder später /pricing: optional, falls Tarife eingeführt werden.
  • /faq: öffentliche FAQ oder FAQ-Auszug.
  • /registrieren beziehungsweise aktuell register.php: Kundenregistrierung.
  • /login beziehungsweise aktuell login.php: Login.
  • /passwort-vergessen beziehungsweise aktuell passwort-vergessen.php: Passwort-Reset.

Landingpage-Inhalte:

  • Hero mit Name Kaffeeliste und kurzer Nutzenbeschreibung.
  • Drei Kernabläufe: Kaffee nehmen, Strich setzen, bei Bedarf bezahlen.
  • Kurzer Blick auf Funktionen: Mitglieder, Guthaben, Einzahlungen, Export, Hinweise.
  • App-Screenshot oder Demo-Ansicht im bestehenden Stil.
  • FAQ-Auszug.
  • Call-to-Action zu Registrierung und Login.

Geschützte App

Die geschützte App bleibt die operative Arbeitsoberfläche.

Empfohlene Routen:

  • /app: Dashboard oder Weiterleitung auf /app/dashboard.
  • /app/dashboard: Meine Kaffeeliste.
  • /app/striche: Eigene Striche erfassen, falls erlaubt.
  • /app/einzahlungen: Zahlungen und PayPal-Optionen.
  • /app/mitglieder: Mitgliederverwaltung.
  • /app/liste: Gesamtübersicht.
  • /app/buchungen: Letzte Einträge und Korrekturen.
  • /app/importe: CSV-Importe.
  • /app/export/pdf: PDF-/Listenexport.
  • /app/hinweise: Hinweise.
  • /app/einstellungen: Tenant-Einstellungen.

Die App sollte eine Sidebar behalten, aber besser gruppiert werden:

  • Persönlich: Meine Kaffeeliste, Namensanpassung, FAQ.
  • Erfassung: Striche, Einzahlungen.
  • Auswertung: Kaffeeliste, Buchungen, Export.
  • Administration: Mitglieder, Hinweise, Einstellungen, Importe.
  • Konto: Kundenkonto und Logout. Public-Links wie Login und Registrierung gehören nicht in die App-Sidebar.

Webspace- und Host-Strategie

Empfohlen für den Start:

  • kaffeeliste.de oder www.kaffeeliste.de für Landingpage, Registrierung und Login.
  • app.kaffeeliste.de für die geschützte App.
  • Keine Wildcard-Subdomains für Kunden.
  • Mandantenauswahl nach Login über Session und tenant_memberships.
  • Kundeneigene feste Domains oder Subdomains erst später gezielt einrichten, wenn DNS und Zertifikat pro Domain sauber geprüft sind.

Damit bleibt der Betrieb webspace-tauglich: Für den Start reichen zwei feste Hosts mit normalen Let's-Encrypt-Zertifikaten. Die fachliche Tenant-Auflösung ist in M3 vorbereitet; produktive Host-Rewrites, Cookie-Domain und Zertifikats- Details gehören zur M8-Betriebshärtung.

Datenmodell

Empfohlen wird ein Shared-Database/Shared-Schema-Modell mit verpflichtendem tenant_id auf allen fachlichen Tabellen.

Kernschema:

tenants(
  id, slug, name, status, timezone, locale, currency_code,
  created_at, updated_at
)

tenant_settings(
  tenant_id, mark_price_cents, self_entry_enabled, paypal_enabled,
  paypal_url_template, sheet_window_days, negative_warning_cents,
  positive_highlight_cents, branding_json, updated_by_user_id
)

users(
  id, email_norm, password_hash, name, email_verified_at,
  mfa_enabled, disabled_at, last_login_at, created_at, updated_at
)

tenant_memberships(
  id, tenant_id, user_id, role, status, invited_at, joined_at,
  created_at, updated_at,
  UNIQUE(tenant_id, user_id)
)

participants(
  id, tenant_id, user_id NULL, display_name, email_norm NULL,
  paypal_name NULL, active, legacy_mitarbeiter_id,
  created_at, updated_at,
  UNIQUE(tenant_id, email_norm)
)

ledger_entries(
  id, tenant_id, participant_id, type, amount_cents, marks_count,
  unit_price_cents, booked_at, source, note, created_by_user_id,
  import_batch_id NULL, legacy_table, legacy_id,
  voided_at NULL, reversal_of_entry_id NULL,
  created_at
)

notices(
  id, tenant_id, message, valid_from, valid_until,
  created_by_user_id, deleted_at, created_at, updated_at
)

payment_import_batches(
  id, tenant_id, original_filename, checksum, status,
  created_by_user_id, created_at
)

payment_import_rows(
  id, batch_id, row_number, participant_id NULL, raw_name,
  amount_cents, booked_at, status, raw_json
)

audit_log(
  id, tenant_id, actor_user_id, action, subject_type, subject_id,
  metadata_json, ip, created_at
)

outbound_emails(
  id, tenant_id, participant_id, template, subject, status,
  sent_at, error, created_at
)

Wichtige Modellierungsregeln:

  • users sind Login-Konten.
  • participants sind Kaffee-Teilnehmer.
  • Ein Teilnehmer kann, muss aber nicht, ein Login-Konto haben.
  • Rollen gehören in tenant_memberships, nicht direkt auf Teilnehmer.
  • Buchungen sollten nicht hart gelöscht werden.
  • Korrekturen laufen über Storno- oder Reversal-Einträge.
  • Legacy-IDs werden bei Migration gespeichert, damit Ergebnisse vergleichbar bleiben.
  • Geldwerte sollten als Cent-Integer gespeichert werden, nicht als Float.

Rollenmodell

Empfohlene Rollen:

  • owner: Kunde verwalten, Billing, Admins, Löschung, Grundeinstellungen.
  • admin: Mitglieder, Hinweise, fachliche Einstellungen, Auswertungen.
  • treasurer: Zahlungen, CSV-Import, Korrekturen, Exporte, Mails.
  • member: eigenes Dashboard, eigene Striche, eigener Anzeigename.
  • viewer: lesende Auswertungen.

Alle Rollen sind tenant-scoped. Ein User kann also bei Kunde A Admin und bei Kunde B nur Mitglied sein.

Authentifizierung und Sicherheit

SaaS-Basis:

  • E-Mail/Passwort-Login mit password_hash.
  • E-Mail-Verifikation.
  • Passwort-Reset.
  • Sichere Session-Cookies.
  • CSRF-Schutz für alle schreibenden Aktionen.
  • Rate-Limits für Login, Registrierung und Passwort-Reset.
  • Zentrale Funktionen oder Middleware: requireLogin, requireTenant, requireRole, csrfToken.

Tenant-Auflösung:

  • Primär serverseitig aus Subdomain, Custom Domain oder tenantgebundener Auswahl nach Login.
  • Nicht allein aus Hidden Inputs oder frei manipulierbaren IDs.
  • Jede fachliche Query braucht einen Tenant-Scope.

Später möglich:

  • LDAP/AD oder SSO pro Kunde als optionaler Identity Provider.
  • MFA für Owner/Admins.
  • Row-Level-Security in der Datenbank.

Design-Leitplanken

Das bestehende Design soll erhalten bleiben.

Beibehalten:

  • Grüner Akzent #38761d.
  • Weiß/Grau als ruhige Grundfläche.
  • Roboto Slab für Überschriften.
  • Open Sans für Fließtext.
  • Sidebar für die geschützte App.
  • Tabellen als primäre Darstellung für operative Daten.
  • Schlichte Formularfelder und Buttons.

Verbessern ohne Relaunch:

  • Hinweise als wiederverwendbare Komponente statt Inline-Style.
  • Einheitliche Erfolgs-, Fehler- und Warnmeldungen.
  • Dashboardwerte als kompakte Statusbereiche.
  • Aktionsleisten über Tabellen.
  • Sidebar-Gruppierung und aktive Navigation.
  • FAQ strukturieren, aber Inhalte nicht stark verändern.

Nicht tun:

  • Kein komplett neues Farbsystem.
  • Keine dekorative Marketingoptik in der App.
  • Keine App-Tabellen in Kartenlandschaften auflösen.
  • Keine Navigationsstruktur, die die heutigen Kernabläufe versteckt.

Meilensteinplan

Übersicht:

Meilenstein Schwerpunkt Hauptergebnis
M0 Baseline Bestand, Sicherheit und Designreferenz sind dokumentiert
M1 Golden Master Legacy-Ergebnisse sind als Vergleichsbasis eingefroren; HTTP-Smoke prüft sichere Seiten
M2 Technisches Fundament Abgeschlossen: Migrationen, Bootstrap, Session, CSRF-Helper und Legacy-Schreibseitenschutz stehen
M3 SaaS-Basis Abgeschlossen: Tenants, User, Registrierung, Login, Rollen, Mail-Links und zentrale Mandantenauswahl funktionieren
M4 Datenmigration Gestartet: Ledger-Tabelle, Legacy-Backfill, Paritätscheck, Ledger-Service und Preview sind umgesetzt
M5 App-Kern Weit fortgeschritten: Kernseiten lesen und schreiben tenant-sicher gegen das Ledger, inklusive Hinweise und rollenbasiertem Zugang; offen ist ein eigener Zahlungs-Screen
M6 Betriebsflows Abgeschlossen: Import, Export, Mail und Jahresabschluss sind auditierbar
M7 Landingpage Public-Seite und Auth-Seiten sind im gemeinsamen Stil nutzbar; spätere Ausbaustufen folgen
M8 Härtung Abgeschlossen: Security-Headers, Rate-Limits, Audit-Log, Mandanten-Isolation/Rollenmatrix getestet, Datenexport, Löschung/Anonymisierung, Backup/Monitoring dokumentiert (CSP als bewusst offener Punkt)
M9 Cutover Produktivumstellung ist vorbereitet und Legacy ist read-only

M0: Planungs- und Sicherheitsbaseline

Ziel: Den aktuellen Zustand verlässlich dokumentieren, bevor umgebaut wird.

Arbeitsartefakte:

  • docs/m0/README.md
  • docs/m0/code-inventory.md
  • docs/m0/security-baseline.md
  • docs/m0/schema-export.sql
  • docs/m0/golden-master-queries.sql
  • docs/m0/screenshot-checklist.md

Schritte:

  • Live-DB-Schema exportieren.
  • Tabellen, Spalten, Indizes und Constraints dokumentieren.
  • Bekannte Jobs/Skripte erfassen, zum Beispiel Mailversand und Jahresauswertung.
  • Secrets inventarisieren und rotieren, insbesondere hart codierte Zugangsdaten.
  • Bestehende Seiten und UI-Zustände per Screenshots festhalten.
  • Fachliche Kernregeln dokumentieren: Saldo, Jahreswerte, Preis pro Strich, 100-Tage-Listen, CSV-Deduplizierung, PDF-Ausgabe.

Ergebnis:

  • Datenkatalog.
  • Prozesslandkarte.
  • Designreferenz.
  • Liste kritischer Sicherheitsrisiken.

Abhängigkeiten:

  • Zugriff auf echte oder anonymisierte Datenbank.
  • Kenntnis der Produktivumgebung.

M1: Golden-Master-Validierung

Ziel: Sicherstellen, dass spätere neue Berechnungen dieselben Ergebnisse wie der Bestand liefern.

Schritte:

  • Referenzdatensatz aus Legacy erzeugen. Erledigt als rekonstruierter Golden Master in der MySQL-Dev-Datenbank.
  • Pro Mitglied berechnen: Gesamteinzahlungen, Gesamtausgaben, Gesamtstriche, aktueller Stand, Jahreswerte. Erledigt mit scripts/check-golden-master.php.
  • CSV-Importfälle sammeln: Treffer, Dubletten, unbekannte Namen. Erledigt im Golden-Master-Check.
  • Testfälle für Guthaben, Schulden, Nullsaldo und inaktive Teilnehmer anlegen. Erledigt.
  • Sichere GET-Seiten per HTTP-Smoke prüfen. Erledigt mit scripts/http-smoke.php.
  • PDF-/Export-Summen als Referenz sichern. Offen, weil die TCPDF-Kopie im Repo unvollständig ist.

Ergebnis:

  • Golden-Master-Daten.
  • Vergleichsqueries oder Vergleichsskript.
  • HTTP-Smoke-Test für sichere UI-Seiten.
  • Akzeptanzkriterien für Migration und neue App.
  • Dokumentierte offene Punkte: PDF-Export/TCPDF, PHPMailer-Abhängigkeit, GET-Nebenwirkungen bei Mailversand und Jahresauswertung.

Abhängigkeiten:

  • M0 abgeschlossen.
  • PDF-/Mail-/Jahresprozesse werden in M6 gezielt neu gestaltet, statt sie in M1 per GET-Smoke auszuführen.

M2: Technisches Fundament

Ziel: Eine saubere Basis für App, Public-Seiten, Auth und Mandanten schaffen.

Schritte:

  • Projektstruktur festlegen: Public-Routes, App-Routes, Views/Templates, Services/Repositories.
  • Zentrales Bootstrap für Config, DB-Verbindung, Session und Fehlerbehandlung. Begonnen mit app/bootstrap.php.
  • Versionierte Migrationen einführen. Begonnen mit database/migrations/0001_legacy_mysql_baseline.sql und scripts/migrate.php.
  • Layouts trennen: Public-Layout und App-Layout. Noch offen; Header/Footer bleiben vorerst Legacy-Wrapper.
  • Bestehende Assets weiterverwenden.
  • CSRF- und Session-Basis einziehen. Session und CSRF-Helper sind vorhanden; globale Erzwingung erfolgt schrittweise pro POST-Seite. Erste Seiten sind abgesichert: hinweise.php, mitarbeiterverwalten.php, namenanpassen.php, index.php, stricheintragen.php, einzahlung.php, letzteneintraege.php, csvupload.php.
  • CSV-Upload außerhalb des Webroots speichern. In M2 als Legacy-Härtung umgesetzt: temporär unter var/uploads, Dateityp-/Größenprüfung und Löschung nach Verarbeitung.
  • Konfigurationswerte aus Code in Umgebung oder Settings verschieben.

Ergebnis:

  • Grundgerüst für neue SaaS-App.
  • Kein fachlicher Rewrite, aber klare Struktur für die Migration.
  • Dokumentation: docs/m2-technical-foundation.md.
  • Status: abgeschlossen für den M2-Scope.

Abhängigkeiten:

  • Entscheidung, ob PHP nativ weitergeführt oder ein Framework genutzt wird.
  • M2 bleibt bewusst ohne Tenant-/User-/Rollen-Tabellen; diese starten in M3.

M3: Mandanten, Registrierung und Login

Ziel: Mehrkundenfähigkeit und Kunden-Onboarding technisch aktivieren.

Schritte:

  • Tabellen für tenants, users, tenant_memberships und tenant_settings anlegen. Vorbereitung dokumentiert in docs/m3-saas-basis-vorbereitung.md: erster Schritt erledigt.
  • Zusätzlich participants als getrennte Kaffee-Teilnehmer-Tabelle anlegen: erledigt.
  • Default-Tenant für den aktuellen Bestand anlegen: erledigt.
  • Bestehende kl_Mitarbeiter idempotent in participants spiegeln: erledigt.
  • Registrierung: Tenant + Owner-User + Default-Settings erzeugen: erster Flow erledigt.
  • Login und Logout bauen: erster Flow erledigt.
  • Passwort-Reset und E-Mail-Verifikation bauen: Dev-Flow mit Single-Use-Tokens und Mail-Transport-Abstraktion erledigt.
  • Tenant-Auflösung definieren: zentrale App-Domain mit Session-Kontext und Mandantenauswahl erledigt; feste Domains vorbereitet, keine Wildcards.
  • Rollenprüfung zentralisieren: erster Owner/Admin-Check erledigt.
  • Erste Admin-/Owner-Seite für Grundeinstellungen: erledigt.
  • Erste Public-Landingpage mit CTA zu Login und Registrierung: erledigt.
  • Login, Registrierung, Passwort-Reset und E-Mail-Verifikation im Public-Stil: erledigt.
  • App-Sidebar ohne Public-Login-/Registrierungslinks: erledigt.

Ergebnis:

  • Ein neuer Kunde kann sich registrieren und in seine eigene App gelangen.
  • Rollen und Tenant-Kontext sind serverseitig verfügbar.
  • Reset- und Verifikationslinks können im Dev-Modus geloggt und produktiv über einen konfigurierten Mail-Transport versendet werden.
  • Status: abgeschlossen für den M3-Scope.

Abhängigkeiten:

  • M2 abgeschlossen.

M4: Migration des fachlichen Kerns

Ziel: Bestehende Kaffeelisten-Daten tenant-sicher übernehmen.

Schritte:

  • Initialen Tenant für den bisherigen Bestand anlegen: erledigt in M3.
  • kl_Mitarbeiter nach participants migrieren: erledigt in M3.
  • admin-Informationen in tenant_memberships oder Admin-Rollen überführen: erledigt in M3.
  • kl_config nach tenant_settings migrieren: erledigt in M3.
  • ledger_entries als tenant-sichere Buchungstabelle anlegen: erledigt.
  • kl_Einzahlungen und kl_Kaffeeverbrauch additiv nach ledger_entries spiegeln: erster Backfill erledigt.
  • Legacy-IDs speichern: erledigt über legacy_table und legacy_id.
  • Salden gegen Golden-Master vergleichen: Kontrollskript erledigt.
  • Ledger-Service für Tenant-Summen, Teilnehmer-Summen und letzte Buchungen: erledigt.
  • Read-only Ledger-Preview für Browservergleich: erledigt.

Ergebnis:

  • Bestehender Datenbestand kann im neuen Modell abgebildet werden.
  • Saldenparität ist über scripts/check-m4-ledger-migration.php nachweisbar.
  • Erste App-Abfragen können über app/ledger.php tenant-sicher aus dem neuen Modell lesen.
  • ledger-preview.php zeigt die neue Ledger-Auswertung ohne Schreibzugriffe.
  • Dokumentation: docs/m4-data-migration.md.

Abhängigkeiten:

  • M1 und M3 abgeschlossen.

M5: Geschützte App-Funktionen

Ziel: Die operativen Kernseiten im neuen Modell bereitstellen.

Schritte:

  • Dashboard Meine Kaffeeliste umsetzen: read-only Ledger-Stand in index.php erledigt; eigene Web-Striche werden als Kompatibilitätsbrücke ins Ledger gespiegelt.
  • Eigene Stricherfassung umsetzen: erledigt über die Selbsteintragung im Dashboard (index.php).
  • Zahlungs-/PayPal-Bereich umsetzen: PayPal-Anzeige und -Links sind über tenant_settings im Dashboard umgesetzt, erledigt als Teil von index.php. Ein eigener /app/einzahlungen-Screen wie ursprünglich in der Zielarchitektur skizziert existiert nicht separat; die Funktion ist bewusst im Dashboard gebündelt statt als eigene Route.
  • Mitgliederverwaltung tenant- und rollenbasiert umsetzen: erledigt. mitarbeiterverwalten.php verwaltet jetzt participants als primäre, tenant-scoped Quelle (nicht mehr die global unscoped kl_Mitarbeiter- Tabelle) und erlaubt Admins, Mitgliedern unabhängig von Name/E-Mail einen Login-Zugang mit Rolle zu gewähren oder zu entziehen (Einladung per Mail über den bestehenden Passwort-Reset-Mechanismus, Entzug als Statuswechsel statt Delete). Nebenbei behoben: eine gespeicherte XSS-Lücke bei Name/ E-Mail und ein Mandanten-Datenleck, weil die alte Version jedem SaaS-Mandanten die komplette Default-Mandanten-Mitgliederliste zeigte.
  • Gesamtübersicht umsetzen: erster read-only Stand in kaffeeliste.php erledigt.
  • Teilnehmerauswertung umsetzen: read-only Stand in teilnehmerauswertung.php erledigt.
  • Letzte Einträge und Korrekturen als Storno statt Delete umsetzen: erledigt in letzteneintraege.php über ledger_void_entry_by_legacy_id(); Legacy- Zeilen werden weiterhin gelöscht, der gespiegelte Ledger-Eintrag bleibt für die Revision erhalten.
  • Sammelerfassung (stricheintragen.php, einzahlung.php) tenant-sicher absichern und ans Ledger anbinden: erledigt. Beide Seiten hatten zuvor keine Zugriffskontrolle außer CSRF; das ist behoben. Zusätzlich lasen beide Seiten ihre Mitarbeiterliste aus der global unscoped kl_Mitarbeiter-Tabelle, was sie für jeden Mandanten außer dem Default-Mandanten unbrauchbar machte. Der Picker kommt jetzt aus participants (tenant-scoped); der Schreibpfad nutzt für den Default-Mandanten weiterhin Dual-Write nach kl_Kaffeeverbrauch/ kl_Einzahlungen, für alle anderen Mandanten schreibt er direkt und ausschließlich ins Ledger.
  • Hinweise als tenant-spezifische Notices umsetzen: erledigt. Neue Tabelle notices mit Soft-Delete, hinweise.php und die Banner-Anzeige in header.php sind tenant-scoped umgestellt; kl_hinweise bleibt nur noch als Golden-Master-Referenz bestehen.

Ergebnis:

  • Ein Kunde kann seine Kaffeeliste operativ nutzen, inklusive Mitgliederpflege und Korrekturen.
  • App sieht weiterhin nach bestehender Kaffeeliste aus.
  • M5-Stand ist in docs/m5-app-kern.md dokumentiert.

Abhängigkeiten:

  • M4 abgeschlossen.

M6: Import, Export und Mail

Ziel: Admin- und Treasurer-Flows produktionsreif machen.

Schritte:

  • CSV-Import mit Vorschau, Dublettenprüfung und Audit bauen: erledigt. Neue Tabellen payment_import_batches/payment_import_rows, zweistufiger Ablauf (Vorschau vor Buchung), Zuordnung per PayPal-Name oder Anzeigename, Dublettenprüfung gegen bestehende Ledger-Zahlungen. csvupload.php hatte zuvor keine Zugriffskontrolle; das ist behoben.
  • Uploads außerhalb des Webroots speichern: erledigt (bereits in M2, weiterhin genutzt).
  • PDF-/Listenexport aus neuem Datenmodell bauen: erledigt. TCPDF war nur teilweise vendort (include/ und Fonts fehlten); nachvendort wurden include/ und die 14 PDF-Standard-Fonts aus dem offiziellen TCPDF-6.6.2-Release. exportKaffeeliste.php hatte zuvor keine Zugriffskontrolle; das ist behoben, die Seite liest jetzt tenant-sicher aus dem Ledger.
  • Mailversand als nachvollziehbaren Versandjob mit Dry-Run und Versandlog gestalten: erledigt. mailversenden.php versendete zuvor ungeprüft bei jedem GET-Request echte Mails über PHPMailer (dessen Quelldateien im Repo gar nicht vorhanden waren) mit fest codierten Alt-Kunden-Inhalten (SMTP, PayPal-Link, FAQ-URL). Ersetzt durch die bestehende saas_send_mail()- Abstraktion aus M3, eine neue outbound_emails-Tabelle als Versandlog und eine standardmäßig aktive Dry-Run-Option.
  • Jahresauswertung beziehungsweise Jahresbuchungen tenant-sicher abbilden: erledigt. Die ursprüngliche Seite verband sich mit fest codierten, kaputten Zugangsdaten selbst zur Datenbank (an config.php vorbei), hatte keine Zugriffskontrolle und verteilte bei jedem Aufruf sofort einen hart codierten Bonus-Topf mit AOK-spezifischem Mailtext. Nach Abstimmung mit dem Kunden als generisches Feature umgesetzt: frei wählbarer Gesamtbetrag, proportionale Verteilung nach Jahresstrichen, Dry-Run, Buchung und Mailversand mit Protokoll.

Ergebnis:

  • Kassenverwaltung ist für reale Betriebsabläufe vollständig.
  • Import/Export/Mail sind auditierbar.
  • Stand: abgeschlossen für den M6-Scope. Dokumentation: docs/m6-import-export-mail.md.

Abhängigkeiten:

  • M5 für die Kernansichten.

M7: Öffentliche Landingpage

Ziel: Werbliche Einstiegseite und Registrierung verfügbar machen, ohne die App-Optik zu verwischen.

Schritte:

  • Public-Layout mit bestehender Typografie und grünem Akzent bauen. Erster Stand als landing.php und assets/css/public.css erledigt.
  • Landingpage-Inhalte erstellen. Erster Stand erledigt.
  • Demo-Screenshot oder Demo-Ansicht einbinden.
  • CTA zu Registrierung und Login. Erledigt.
  • Login, Registrierung und Passwort-Reset in den Public-Stil integrieren. Erledigt.
  • App-Sidebar von Public-Links trennen. Erledigt.
  • FAQ-Auszug strukturieren.
  • Keine App-Sidebar im Public-Bereich.

Ergebnis:

  • Interessenten verstehen das Produkt und können sich registrieren.
  • Bestehende App bleibt optisch eigenständig und arbeitsorientiert.

Abhängigkeiten:

  • M3 für Registrierung.
  • M5 für echte App-Screens oder Demo.

M8: Härtung, Datenschutz und Betrieb

Ziel: Die SaaS-App für mehrere Kunden sicher betreiben.

Schritte:

  • Backups und Restore-Prozess definieren: erledigt (dokumentiert). docs/betrieb-backup-monitoring.md beschreibt den täglichen mysqldump-Cron-Job, Aufbewahrung, Restore-Befehl und den vierteljährlichen Restore-Test.
  • Monitoring und Fehlerlogging einrichten: erledigt (dokumentiert). Produktive PHP-Fehlerkonfiguration sowie die aktiv zu beobachtenden Signale (audit_log, rate_limit_attempts, outbound_emails) sind in docs/betrieb-backup-monitoring.md festgehalten.
  • Audit-Log für Admin-Aktionen prüfen: erledigt. Neue Tabelle audit_log, protokolliert Mitgliederverwaltung, Storno, Mandant-Einstellungen, Hinweise, CSV-Import, Jahresbonus und Live-Mailversand; sichtbar für Owner/Admin auf mandant-einstellungen.php.
  • Datenexport pro Tenant: erledigt. datenexport.php liefert Owner/Admin einen vollständigen JSON-Export des eigenen Mandanten (Stammdaten, Teilnehmer, Mitglieder/Rollen, Buchungen, Hinweise, Importe, Mail-Log, Admin-Protokoll) ohne Passwörter, protokolliert im Audit-Log.
  • Lösch-/Anonymisierungsprozess für Teilnehmer und Kunden: erledigt. Teilnehmer werden anonymisiert statt gelöscht (Buchungshistorie bleibt für die Kassenführung erhalten), Mandanten können sich über mandant-loeschen.php mit Bestätigungseingabe vollständig selbst löschen (Cascade über Fremdschlüssel); der migrierte Default-Mandant ist davon ausgenommen.
  • Rate-Limits und Security Headers: erledigt. Globale Security-Headers über app/bootstrap.php (ohne CSP, siehe docs/m8-haertung.md), DB-gestützte Rate-Limits für Login, Registrierung und Passwort-Reset.
  • Mandanten-Isolation testen: erledigt. scripts/check-m8-tenant-isolation.php legt zwei isolierte Test-Mandanten an und prüft Lese- und Schreibzugriffe über den jeweils falschen Mandanten (10 Assertions, grün).
  • Rollenmatrix testen: erledigt. scripts/check-m8-role-matrix.php prüft alle fünf Rollen gegen alle rollen-geschützten Seiten per echtem HTTP-Login (55 Assertions, grün).

Ergebnis:

  • SaaS ist betrieblich und datenschutzseitig belastbarer.
  • Stand: abgeschlossen für den M8-Scope, mit einer bewusst offenen Ausnahme (Content-Security-Policy, siehe docs/m8-haertung.md). Dokumentation: docs/m8-haertung.md, docs/betrieb-backup-monitoring.md.

Abhängigkeiten:

  • M3 bis M6.

M9: Cutover und Legacy-Archiv

Ziel: Produktive Umstellung ohne Datenverlust.

Schritte:

  • Legacy für Schreibzugriffe sperren.
  • Finalen Delta-Export ziehen.
  • Migration ausführen.
  • Rowcounts, Salden, PDF-Stichproben und Importhistorie prüfen.
  • DNS oder Reverse Proxy umschalten.
  • Legacy 30 bis 90 Tage read-only als Archiv behalten.
  • Rollback-Fenster und Vorgehen dokumentieren.

Ergebnis:

  • Neuer SaaS-Betrieb ist live.
  • Alter Bestand bleibt für Rückfragen lesbar.

Abhängigkeiten:

  • M8 abgeschlossen.

Empfohlene MVP-Reihenfolge

Für den ersten produktnahen MVP sollten nicht alle Randmodule gleichzeitig umgebaut werden.

MVP-Paket:

  1. Tenant, User, Login, Registrierung.
  2. Mail-Links, zentrale Mandantenauswahl und erste Landingpage.
  3. Teilnehmer, Settings, Ledger.
  4. Dashboard, Striche, Einzahlungen, Gesamtübersicht.
  5. Mitgliederverwaltung.
  6. Hinweise.
  7. CSV-Import, Export.

Später:

  • Umfrage-Modul.
  • SSO/LDAP pro Kunde.
  • Billing/Tarife.
  • MFA.
  • Custom Domains.
  • Erweiterte Mandanten-Branding-Optionen.

Validierung

Fachliche Prüfungen:

  • Saldo je Teilnehmer alt gegen neu.
  • Jahreswerte alt gegen neu.
  • Gesamtstriche alt gegen neu.
  • Einzahlungen alt gegen neu.
  • Export-Summen alt gegen neu.
  • CSV-Import: identische Treffer- und Dublettenlogik.

Sicherheitsprüfungen:

  • Fremde Tenant-IDs liefern keine Daten.
  • Fremde Teilnehmer-IDs liefern keine Daten.
  • Admin-Funktionen sind ohne Rolle blockiert.
  • Alle POST-Aktionen brauchen CSRF.
  • Uploads sind nicht direkt ausführbar.
  • Ausgabe von Namen, Mails und Hinweisen ist escaped.

UX-Prüfungen:

  • Kernseiten bleiben optisch wiedererkennbar.
  • Sidebar bleibt für App-Nutzer vertraut.
  • Landingpage zeigt keine App-Adminnavigation.
  • Mobile Darstellung bleibt bedienbar.
  • Tabellen bleiben lesbar.

Risiken und Gegenmaßnahmen

Risiko Gegenmaßnahme
Tenant-Leak durch vergessene Query-Filter Zentrale Repository-/Query-Schicht, Tests mit zwei Tenants
Falsche Salden nach Migration Golden-Master-Vergleich vor Cutover
Harte Deletes verlieren Audit-Historie Storno-/Reversal-Modell für Buchungen
Login und Teilnehmer werden vermischt users und participants strikt trennen
Bestehendes Design driftet weg Designreferenz und App-Komponenten definieren
CSV-Upload unsicher Upload außerhalb Webroot, Dateitypprüfung, Importvorschau
Mailversand nicht nachvollziehbar Versandlog, Dry-Run, Job-Status
Unvollständige Vendor-Kopien für PDF/Mail Dependencies sauber vendoren oder ersetzen, Smoke-Tests danach erweitern. TCPDF in M6 nachvendort (nur include/ und die 14 Standard-Fonts); PHPMailer weiterhin unvollständig, siehe M6-Mailversand
AD/LDAP blockiert SaaS-Onboarding E-Mail/Passwort als Basis, SSO später optional
Kein DB-Schema im Repo Schema exportieren und Migrationen einführen

Offene Entscheidungen

  • Soll die neue App weiterhin nativ in PHP entstehen oder mit einem Framework?
  • Soll SQL Server bleiben oder mittelfristig eine andere Datenbank genutzt werden?
  • Wie sollen Kunden-Tenants adressiert werden: Subdomain, Pfad oder Auswahl nach Login?
  • Welche Rolle soll treasurer haben: eigene Rolle oder Teil von admin? Entschieden mit der M5-Zugangsvergabe: treasurer ist eine eigene, über mitarbeiterverwalten.php vergebbare Rolle, getrennt von admin.
  • Wird das Umfrage-Modul Teil des SaaS-Produkts oder nur archiviert?
  • Braucht der MVP schon Tarife/Billing oder erstmal nur Registrierung?
  • Sollen bestehende Kunden per Einladung oder per Self-Service onboarden?

Nächster konkreter Arbeitsschritt

Als nächstes sollte M0 gestartet werden:

  1. Produktiv-/Testdatenbankschema exportieren.
  2. Screenshot-Referenz der wichtigsten Seiten erstellen.
  3. Golden-Master-Auswertungen definieren.
  4. Entscheidung treffen, ob der neue SaaS-Kern im bestehenden PHP-Stil oder mit einem Framework aufgebaut wird.