feat(mandant): Betriebsmodus, Kundenverwaltung und Einstellungen
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s

Laufzettel läuft jetzt in zwei Betriebsmodi, aber nicht in zwei Codepfaden. Jede
kundenbezogene Zeile trägt ohnehin schon eine Kunden-Zuordnung; im Modus INTERN
ist das die eine Organisation, die in der Oberfläche nirgends erscheint. Der
Wechsel ist deshalb eine Datenänderung und keine Migration — dieselbe Zeile
bleibt, nichts hängt um.

src/server/mandant.ts ist die eine Stelle, an der die Mandantenregel steht:
kundeErmitteln verwirft im Modus INTERN eine mitgeschickte kundeId, verlangt im
Modus DIENSTLEISTUNG eine Wahl und prüft, ob der Benutzer den Kunden überhaupt
sehen darf. Die Sichtbarkeit steckte zunächst in einer zweiten Funktion daneben,
an die jeder künftige Aufrufer selbst hätte denken müssen; da #009 diese Signatur
übernimmt, ist sie jetzt eingebaut.

Der unabhängige Review fand drei Blocker, alle behoben.

Die Lesemodelle wandten die Mandantensicht nicht an. kundenListe gab jedem alle
Kunden zurück, und sichtbareKundenIds hatte in der Produktion keinen einzigen
Aufrufer — ein gebundener Techniker hätte Namen, Notizen und Bestandszahlen
fremder Kunden gesehen. Die Funktionen verlangen jetzt den Benutzer und filtern
selbst, wie CLAUDE.md es verlangt.

Mein Kommentar "Alles in einer Transaktion" war falsch: der Protokolleintrag
stand dahinter. Schlug er fehl, war der Modus gewechselt und der Vorgang stand
nirgends. Und Prüfung und Vollzug lagen auseinander, sodass zwei gleichzeitige
Wechsel beide durchliefen und ein zwischendurch angelegter Kunde zu Modus INTERN
bei zwei Kunden führte. Beides reproduziert, beides geschlossen: alles in einer
Transaktion, mit pg_advisory_xact_lock als erster Anweisung.

Dazu sechs kleinere Befunde: ASCII-Faltung in Texten, die Menschen lesen (die
Regel gilt für Bezeichner, nicht für Fließtext — steht jetzt so in CLAUDE.md);
eine Erfolgsmeldung, die nach dem Wechsel unsichtbar blieb; eine Zusage im
Dialog, die der Code nicht durchsetzt; fehlende Fehlerbehandlung beim
Archivieren; ein Handy-Screenshot, der das Navigationsmenü statt des Dialogs
zeigte.

Der Wechsel wurde nicht nur im Service geprüft, sondern per Playwright durch die
Oberfläche gefahren: anmelden, Dialog aufklappen, bestätigen, und danach steht
die Navigation auf Kunden.

336 Tests.

Closes #8

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-25 07:01:46 +02:00
co-authored by Claude Opus 5
parent 40e95dbe5f
commit 8e67591912
43 changed files with 2729 additions and 53 deletions
+15
View File
@@ -40,6 +40,16 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
- Benutzerverwaltung für Administratoren: Rolle setzen, Konto abschalten, lokales Konto
anlegen (#007)
- Navigation zeigt nur, was die eigene Rolle darf (#007)
- Betriebsmodus **Intern** und **Dienstleistung**, umschaltbar ohne Neustart. Im Modus Intern
erscheint die eigene Organisation nirgends; der Wechsel nimmt alle Geräte, Personen,
Abteilungen und Läufe mit, weil jede Zeile ohnehin schon eine Kunden-Zuordnung trägt (#008)
- Bestätigungsdialog für den Moduswechsel, der die Folgen aus derselben Prüfung zeigt, die der
Wechsel danach durchsetzt (#008)
- Kundenverwaltung im Modus Dienstleistung: anlegen, bearbeiten, archivieren statt löschen (#008)
- Admin → Einstellungen: Betriebsmodus, Name der eigenen Organisation, globale Namensregeln,
Sperr-OU und die Anzeige der Adresse, die in den QR-Codes steckt (#008)
- Navigation und Beschriftungen folgen dem Betriebsmodus — ein Codepfad, nur ein- und
ausgeblendete Einträge (#008)
### Sicherheit
@@ -62,3 +72,8 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
`LESER` herabzustufen (#007)
- Der letzte aktive Administrator kann sich weder herabstufen noch abschalten (#007)
- Beim Abschalten eines Kontos werden laufende Sitzungen sofort ungültig (#007)
- Im Modus Intern wird eine vom Client mitgeschickte Kunden-Zuordnung verworfen, nicht bloß
abgelehnt — es gibt in diesem Modus keine Kundenwahl (#008)
- Der Rückweg nach Intern wird abgelehnt, solange mehr als ein nicht archivierter Kunde
existiert (#008)
- Die eigene Organisation lässt sich nicht archivieren (#008)