Ergaenzt die beiden letzten offenen M7-Punkte und schaerft nebenbei die Copy insgesamt: - Neue Sektion 'So funktioniert's' mit dem im Plan vorgesehenen Drei-Schritte-Ablauf (Kaffee nehmen, Strich setzen, bei Bedarf bezahlen), bisher komplett gefehlt. - Funktionsuebersicht von drei auf sechs Karten erweitert (Mitglieder/ Rollen, Guthaben/Einzahlungen, PayPal, CSV-Import/-Export, Mandantenfaehigkeit, Hinweise/Info-Mails) statt der bisherigen, eher technischen Kurzbeschreibung. - Demo-Screenshot: bereinigter Screenshot der Gesamtuebersicht mit Testdaten (keine echten Kundendaten) aus docs/m0/screenshots/ als assets/images/demo-kaffeeliste.png eingebunden. - FAQ-Auszug: fuenf produktbezogene Fragen (Einrichtung, Mandanten- trennung, Rollen, Export/Loeschung, PayPal) direkt auf der Landingpage. Bewusst nicht die bestehende faq.php excerpted, da die komplett AOK-spezifisch ist (Kaffeemaschinen-Bedienung, interner Ansprechpartner) und fuer Interessenten nicht generisch verstaendlich waere. - Neue CSS-Klassen fuer Schritte/Demo-Rahmen/FAQ-Grid/Abschluss-CTA in assets/css/public.css, konsistent mit dem bestehenden Grundton (gruener Akzent, schlichte Kartenoptik, keine Marketing-Verspieltheit). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
31 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_USERund LDAP. - Die Mailadresse des Nutzers wird in
functionsLDAP.phpermittelt. - Rollen liegen als Boolean
admindirekt aufkl_Mitarbeiter. - Datenzugriff findet direkt in den einzelnen PHP-Seiten per
sqlsrv_querystatt. - 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.cssund dem HTML5-UP-Layout. - Die App-Navigation sitzt faktisch in
footer.php;nav.phpist leer.
Grundsatzentscheidung
Der Umbau sollte nicht mit einer rein kosmetischen Landingpage starten, sondern mit einem stabilen SaaS-Fundament:
- Bestand dokumentieren und fachliche Ergebnisse einfrieren.
- Mandantenmodell, Auth und Rollen sauber aufbauen.
- Bestehende Fachlogik schrittweise tenant-sicher übernehmen.
- 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./preiseoder später/pricing: optional, falls Tarife eingeführt werden./faq: öffentliche FAQ oder FAQ-Auszug./registrierenbeziehungsweise aktuellregister.php: Kundenregistrierung./loginbeziehungsweise aktuelllogin.php: Login./passwort-vergessenbeziehungsweise aktuellpasswort-vergessen.php: Passwort-Reset.
Landingpage-Inhalte:
- Hero mit Name
Kaffeelisteund 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.deoderwww.kaffeeliste.defür Landingpage, Registrierung und Login.app.kaffeeliste.defü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:
userssind Login-Konten.participantssind 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 | Abgeschlossen: Public-Seite mit Kernabläufen, Funktionsübersicht, Demo-Screenshot und FAQ-Auszug; Auth-Seiten im gemeinsamen Stil |
| 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.mddocs/m0/code-inventory.mddocs/m0/security-baseline.mddocs/m0/schema-export.sqldocs/m0/golden-master-queries.sqldocs/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.sqlundscripts/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_membershipsundtenant_settingsanlegen. Vorbereitung dokumentiert indocs/m3-saas-basis-vorbereitung.md: erster Schritt erledigt. - Zusätzlich
participantsals getrennte Kaffee-Teilnehmer-Tabelle anlegen: erledigt. - Default-Tenant für den aktuellen Bestand anlegen: erledigt.
- Bestehende
kl_Mitarbeiteridempotent inparticipantsspiegeln: 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_Mitarbeiternachparticipantsmigrieren: erledigt in M3.admin-Informationen intenant_membershipsoder Admin-Rollen überführen: erledigt in M3.kl_confignachtenant_settingsmigrieren: erledigt in M3.ledger_entriesals tenant-sichere Buchungstabelle anlegen: erledigt.kl_Einzahlungenundkl_Kaffeeverbrauchadditiv nachledger_entriesspiegeln: erster Backfill erledigt.- Legacy-IDs speichern: erledigt über
legacy_tableundlegacy_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.phpnachweisbar. - Erste App-Abfragen können über
app/ledger.phptenant-sicher aus dem neuen Modell lesen. ledger-preview.phpzeigt 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 Kaffeelisteumsetzen: read-only Ledger-Stand inindex.phperledigt; 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_settingsim Dashboard umgesetzt, erledigt als Teil vonindex.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.phpverwaltet jetztparticipantsals primäre, tenant-scoped Quelle (nicht mehr die global unscopedkl_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.phperledigt. - Teilnehmerauswertung umsetzen: read-only Stand in
teilnehmerauswertung.phperledigt. - Letzte Einträge und Korrekturen als Storno statt Delete umsetzen: erledigt
in
letzteneintraege.phpüberledger_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 unscopedkl_Mitarbeiter-Tabelle, was sie für jeden Mandanten außer dem Default-Mandanten unbrauchbar machte. Der Picker kommt jetzt ausparticipants(tenant-scoped); der Schreibpfad nutzt für den Default-Mandanten weiterhin Dual-Write nachkl_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
noticesmit Soft-Delete,hinweise.phpund die Banner-Anzeige inheader.phpsind tenant-scoped umgestellt;kl_hinweisebleibt 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.mddokumentiert.
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.phphatte 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 wurdeninclude/und die 14 PDF-Standard-Fonts aus dem offiziellen TCPDF-6.6.2-Release.exportKaffeeliste.phphatte 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.phpversendete 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 bestehendesaas_send_mail()- Abstraktion aus M3, eine neueoutbound_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.phpvorbei), 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.phpundassets/css/public.csserledigt. - Landingpage-Inhalte erstellen: erledigt und überarbeitet. Klarere Nutzenbeschreibung, expliziter Drei-Schritte-Ablauf (Kaffee nehmen → Strich setzen → bei Bedarf bezahlen) und erweiterte Funktionsübersicht (Mitglieder/Rollen, Guthaben/Einzahlungen, PayPal, CSV-Import/-Export, Mandantenfähigkeit, Hinweise/Info-Mails).
- Demo-Screenshot oder Demo-Ansicht einbinden: erledigt. Bereinigter
Screenshot der Gesamtübersicht (Testdaten, keine echten Kundendaten)
aus
docs/m0/screenshots/alsassets/images/demo-kaffeeliste.png. - 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: erledigt. Fünf produktbezogene Fragen
(Einrichtung, Mandantentrennung, Rollen, Export/Löschung, PayPal) direkt
auf der Landingpage, mit Verweis auf die vollständige FAQ. Die
bestehende
faq.phpbleibt bewusst unverändert AOK-spezifisch (Kaffeemaschine, Ansprechpartner) und wurde nicht 1:1 auf die Landingpage übernommen. - Keine App-Sidebar im Public-Bereich.
Ergebnis:
- Interessenten verstehen das Produkt und können sich registrieren.
- Bestehende App bleibt optisch eigenständig und arbeitsorientiert.
- Stand: abgeschlossen.
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.mdbeschreibt den täglichenmysqldump-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 indocs/betrieb-backup-monitoring.mdfestgehalten. - 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 aufmandant-einstellungen.php. - Datenexport pro Tenant: erledigt.
datenexport.phpliefert 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.phpmit 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, siehedocs/m8-haertung.md), DB-gestützte Rate-Limits für Login, Registrierung und Passwort-Reset. - Mandanten-Isolation testen: erledigt.
scripts/check-m8-tenant-isolation.phplegt 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.phpprü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:
- Tenant, User, Login, Registrierung.
- Mail-Links, zentrale Mandantenauswahl und erste Landingpage.
- Teilnehmer, Settings, Ledger.
- Dashboard, Striche, Einzahlungen, Gesamtübersicht.
- Mitgliederverwaltung.
- Hinweise.
- 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 sollEntschieden mit der M5-Zugangsvergabe:treasurerhaben: eigene Rolle oder Teil vonadmin?treasurerist eine eigene, übermitarbeiterverwalten.phpvergebbare Rolle, getrennt vonadmin.- 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:
- Produktiv-/Testdatenbankschema exportieren.
- Screenshot-Referenz der wichtigsten Seiten erstellen.
- Golden-Master-Auswertungen definieren.
- Entscheidung treffen, ob der neue SaaS-Kern im bestehenden PHP-Stil oder mit einem Framework aufgebaut wird.