Files
kaffeekasse-saas/docs/backoffice.md
T
clemensandClaude Opus 5 3f6866e075 Menue folgt auch den Schaltern des Mandanten
Bisher blendete das Menue nur aus, was der Betreiber gesperrt hatte. Hat
der Mandant selbst eine Funktion abgeschaltet - etwa "PayPal anbieten" in
den Mandant-Einstellungen - blieb der Menuepunkt stehen und fuehrte auf
eine Seite ohne Zweck.

app_feature_available() prueft nun beide Ebenen, app_feature_tenant_switch()
haelt die Zuordnung Funktion -> Mandanten-Schalter. Navigation und Anleitung
nutzen die neue Pruefung; paypal-zuordnung.php sperrt sich beim direkten
Aufruf ebenfalls und verweist dabei auf die Mandant-Einstellungen statt auf
den Betreiber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:16:30 +02:00

182 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Back-Office (Platform-Admin)
Stand: 2026-07-16
Ergänzung außerhalb der ursprünglichen M0M9-Meilensteine: ein
Betreiber-Zugang, der mandantenübergreifend alle Kunden einsehen kann
getrennt von der normalen, streng mandantengebundenen Rollenlogik.
## Design-Entscheidung: getrennt von `tenant_memberships`
Platform-Admin-Rechte sind bewusst **kein** Teil von
`saas_user_has_role()`/`tenant_memberships`, sondern eine eigene, komplett
separate Tabelle `platform_admins` (`user_id``users.id`). Das ist
kein Zufall: Alle bestehenden Mandanten-Seiten und die automatisierten
Isolationstests (`scripts/check-m8-tenant-isolation.php`,
`scripts/check-m8-role-matrix.php`) verlassen sich darauf, dass ein
Login niemals automatisch mandantenübergreifenden Zugriff bekommt. Ein
Platform-Admin-Flag direkt in `tenant_memberships` oder `users` einzubauen
hätte dieses Fundament unterlaufen können. Stattdessen prüft
ausschließlich der neue Code-Pfad in `app/platform-admin.php`
(`app_require_platform_admin()`) auf Back-Office-Seiten die
Platform-Admin-Eigenschaft; alle bestehenden Seiten sind unverändert und
bleiben strikt mandantengebunden.
## Umgesetzte Dateien
```text
database/migrations/0013_saas_platform_admins.sql
app/platform-admin.php
scripts/grant-platform-admin.php
backoffice.php
backoffice-mandant.php
backoffice-export.php
```
## Erster Platform-Admin
Es gibt bewusst **keine Weboberfläche**, um den ersten Platform-Admin zu
setzen das wäre ein öffentlich erreichbarer "werde Admin"-Endpunkt und
ein erhebliches Risiko. Stattdessen: Person registriert sich normal als
Mandant (oder nutzt einen bestehenden Login), danach per Shell-Zugriff auf
dem Server:
```bash
php scripts/grant-platform-admin.php person@example.com
```
Das Skript prüft, dass der Account existiert, und legt nur dann den
`platform_admins`-Eintrag an. Zugang entziehen aktuell nur per direktem
`DELETE FROM platform_admins WHERE user_id = ?` (keine UI dafür aus
demselben Grund wie beim Setzen).
## Funktionsumfang
- `backoffice.php`: Übersicht aller Mandanten mit Kürzel, Status,
Erstelldatum, Teilnehmerzahl (aktiv/gesamt) und Saldensumme.
- `backoffice-mandant.php?tenant_id=X`: Ansicht eines einzelnen Mandanten
Einstellungen, Mitglieder mit Rolle, letzte 20 Buchungen, letzte 20
Admin-Aktionen, dazu die Funktions-Schalter (siehe unten). Absichtlich
**kein** Schreibzugriff auf die eigentlichen Mandantendaten (Buchungen,
Mitglieder, Einstellungen des Kunden) von hier aus, um das Risiko einer
versehentlichen Fremdänderung auszuschließen.
- `backoffice-export.php`: nutzt dieselbe `app_export_tenant_data()`-
Funktion wie der Selbstbedienungs-Export, aber ausgelöst durch den
Platform-Admin für einen beliebigen Mandanten.
## Funktions-Schalter je Mandant
Stand: 2026-08-17. Zentralstelle des Betreibers, um einzelne Funktionen
pro Mandant freizuschalten oder zu sperren gepflegt in
`backoffice-mandant.php`, umgesetzt in `app/features.php` und der Tabelle
`tenant_features` (Migration `0025_tenant_features.sql`).
Schaltbar sind: FAQ-Seite, Striche selbst eintragen, Kaffeeliste als PDF,
PayPal-Zahlungseingang, CSV-Import, Info-Mails und Erinnerungen,
Jahresabschluss, Datenexport, eigenes Design.
Design-Entscheidungen:
- **Getrennt von `tenant_settings`.** Dort stellt der Kunde ein, *wie*
eine Funktion arbeitet; hier entscheidet der Betreiber, ob sie ihm
überhaupt zur Verfügung steht. Beide Prüfungen stehen nebeneinander,
z. B. beim Selbsteintrag: `self_entry_enabled` (Kunde) **und**
Feature `self_entry` (Betreiber).
- **Ohne Zeile = freigeschaltet.** `tenant_features` hält nur die
Abweichungen vom Standard; ein neuer Mandant bekommt automatisch den
vollen Funktionsumfang, ohne dass der Betreiber etwas einschalten muss.
Fehlt die Tabelle (Migration noch nicht ausgerollt), bleibt ebenfalls
alles freigeschaltet eine ausstehende Migration darf keine Funktion
sperren.
- **Gesperrt heißt unsichtbar.** Die betroffenen Menüpunkte und
Schaltflächen verschwinden (`footer.php`, `kaffeeliste.php`,
`einzahlung.php`, `konto.php`); ein direkter Aufruf der Seite endet mit
einem Hinweis statt mit einem Fehler. Die POST-Verarbeitung der
betroffenen Seiten hängt an derselben Prüfung, eine gesperrte Funktion
lässt sich also auch nicht per Formular-POST auslösen.
- **Abgeschaltet vom Kunden heißt ebenfalls unsichtbar.** Für Funktionen
mit eigenem Schalter in den Mandant-Einstellungen zieht `footer.php`
(und die Anleitung) `app_feature_available()` heran: erst die
Freischaltung durch den Betreiber, dann der Schalter des Kunden. Die
Zuordnung steht in `app_feature_tenant_switch()` aktuell
`paypal_inbox``paypal_enabled` und `self_entry`
`self_entry_enabled`. Wer PayPal nicht als Zahlungsweg anbietet, sieht
den Menüpunkt „PayPal-Zahlungen" also gar nicht erst; beim direkten
Aufruf erscheint statt des Betreiber-Hinweises
`app_feature_tenant_notice_html()` mit dem Weg zurück in die
Mandant-Einstellungen.
- Jede Änderung landet als `backoffice.features_updated` im Audit-Log des
betroffenen Mandanten wie jeder andere Back-Office-Zugriff auch.
Das Back-Office ist außerdem jetzt für Platform-Admins im Menü verlinkt
(Gruppe „Betreiber" in `footer.php`); vorher war es nur über die direkte
URL erreichbar.
## Eigenes Design je Mandant
Stand: 2026-08-17, hängt am Feature `branding`. Der Kunde hinterlegt in
`mandant-einstellungen.php` eine Akzentfarbe und ein Logo:
- Die Farbe (`tenant_settings.brand_color`, validiert als `#rrggbb`) wird
in `header.php` als kleines Stylesheet eingebettet
(`app/branding.php`), das die Akzentfarbe des Templates überschreibt.
Eine eigene CSS-Datei je Mandant wäre ein zusätzlicher Request für
wenige Zeilen.
- Das Logo (`tenant_settings.brand_logo`) liegt wie das PDF-Wasserzeichen
in `var/tenant_logos` und ist damit **nicht** direkt per URL abrufbar
sonst wäre jedes Kundenlogo unter einer ratbaren Adresse öffentlich.
Ausgeliefert wird es über `tenant-logo-anzeigen.php`, das ausschließlich
das Logo des Mandanten des angemeldeten Nutzers ausgibt.
- Wasserzeichen (Ausdruck) und Marken-Logo (Oberfläche) sind getrennte
Dateien mit eigenem Namenspräfix (`logo_` bzw. `brand_`).
- Sperrt der Betreiber `branding`, verschwindet der Abschnitt aus den
Einstellungen und die Oberfläche fällt aufs Standarddesign zurück. Die
gespeicherte Farbe bleibt erhalten und gilt nach einer erneuten
Freischaltung wieder.
## Verhältnis zum Selbstbedienungs-Export (`datenexport.php`)
Bewusste Entscheidung: Der bestehende Selbstbedienungs-Export für
Mandanten-Owner/Admin (`datenexport.php`, aus M8) bleibt zusätzlich
bestehen, statt ihn durch das Back-Office zu ersetzen. Begründung: Der
Mandant ist im Auftragsverarbeitungs-Verhältnis der Verantwortliche
(Controller), der Betreiber dieser App der Auftragsverarbeiter
(Processor). Art. 15/20 DSGVO geben den betroffenen Personen ein
Auskunfts-/Portabilitätsrecht, und Art. 28 DSGVO verpflichtet den
Auftragsverarbeiter, den Verantwortlichen bei der Erfüllung dieser
Rechte zu unterstützen sowie Daten am Vertragsende zurückzugeben ein
jederzeit verfügbarer Selbstbedienungs-Export erfüllt genau das. Das
Back-Office ergänzt das um einen Betreiber-seitigen Zugriff für Support,
Migrationen oder eigene Nachweispflichten, ersetzt den
Mandanten-Selbstexport aber nicht.
## Audit-Trail
Jede Back-Office-Ansicht und jeder Back-Office-Export wird im Audit-Log
**des betroffenen Mandanten** protokolliert
(`backoffice.tenant_viewed`, `backoffice.tenant_exported`, mit dem
Platform-Admin als `actor_user_id`). Ein Mandant sieht damit über sein
eigenes Protokoll (`mandant-einstellungen.php`), wann der Betreiber auf
seine Daten zugegriffen hat Transparenzpflicht statt stiller
Einsicht.
## Prüfstatus
- `scripts/check-m8-tenant-isolation.php`: weiterhin grün mit 10
Assertions das Back-Office berührt die geprüften Pfade nicht.
- `scripts/check-m8-role-matrix.php`: weiterhin grün mit 55 Assertions.
- HTTP-Smoke: grün mit 30 geprüften Seiten (zwei neue Login-Schutz-Checks
für `backoffice.php`/`backoffice-mandant.php`).
- Golden Master: grün mit 104 Assertions.
Live getestet: eigens angelegter Test-Mandant, per CLI-Skript zum
Platform-Admin gemacht, Back-Office-Übersicht zeigt korrekt alle
Mandanten (inklusive der echten Bestandsmandanten), Detailansicht und
Export für den eigenen Test-Mandanten funktionieren, Audit-Log-Einträge
korrekt geschrieben. Kritischer Isolationstest bestätigt: derselbe
Platform-Admin sieht auf regulären Mandanten-Seiten (`kaffeeliste.php`)
weiterhin ausschließlich seinen eigenen Mandanten. Ein zweiter,
eingeloggter, aber nicht privilegierter Testnutzer bekommt auf
`backoffice.php` korrekt `403 Forbidden`. Alle Testdaten anschließend
vollständig entfernt.