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>
775 lines
28 KiB
Markdown
775 lines
28 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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.
|