Files
kaffeekasse-saas/docs/saas-umstrukturierungsplan.md
T
clemensandClaude Sonnet 5 536ef2ead2 M6: Jahresabschluss als generisches Feature statt AOK-spezifischem Bonus-Skript
jahresauswertung.php verband sich bisher mit fest codierten (kaputten)
Zugangsdaten selbst zur Datenbank statt ueber config.php, hatte keine
Zugriffskontrolle und kein CSRF, und verteilte bei jedem Aufruf sofort
einen hart codierten Bonus-Topf (490 Striche a 0,20 Euro) per PHPMailer
(dessen Quelldateien im Repo fehlen) mit AOK-spezifischem Mailtext.

Nach Abstimmung mit dem Kunden als generisches, mandantenfaehiges Feature
neu gebaut statt nur deaktiviert oder rein lesend umgesetzt:

- Admin gibt einen frei waehlbaren Gesamtbetrag ein, das System verteilt
  ihn proportional zu den Jahresstrichen auf alle aktiven Mitglieder.
- Standardmaessig aktive Dry-Run-Checkbox zeigt die Verteilung, ohne zu
  buchen oder Mails zu verschicken.
- Bestaetigter Lauf bucht ueber dasselbe Zweig-Muster wie ueberall
  (Default-Mandant Dual-Write, andere Mandanten ledger_record_payment)
  und verschickt personalisierte Mails ueber saas_send_mail(), protokolliert
  im outbound_emails-Versandlog.
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- http-smoke.php: jahresauswertung.php jetzt regulaerer Check statt
  uebersprungenem unsicherem Aufruf; damit sind keine Seiten mehr
  uebersprungen oder als bekannter offener Punkt markiert (26/26 gruen).

Live getestet: Dry-Run mit korrekter proportionaler Verteilung (Summe
ergibt exakt den Gesamtbetrag), Live-Lauf bucht und versendet korrekt,
Testdaten anschliessend vollstaendig entfernt.

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

28 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 Betrieb, Datenschutz, Monitoring und Isolation sind geprüft
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.
  • Monitoring und Fehlerlogging einrichten.
  • Audit-Log für Admin-Aktionen prüfen.
  • Datenexport pro Tenant.
  • Lösch-/Anonymisierungsprozess für Teilnehmer und Kunden.
  • Rate-Limits und Security Headers.
  • Mandanten-Isolation testen.
  • Rollenmatrix testen.

Ergebnis:

  • SaaS ist betrieblich und datenschutzseitig belastbarer.

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.