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>
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_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 | 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.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. 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:
- 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.