66 lines
5.3 KiB
Markdown
66 lines
5.3 KiB
Markdown
# #008 M3 – Betriebsmodus und Kunden
|
||
|
||
**Status:** erledigt
|
||
**Meilenstein:** v0.1.0
|
||
**Labels:** phase-1, backend, frontend
|
||
**Geschätzt:** 6 h **Tatsächlich:** 1 h 20 min
|
||
|
||
## Ziel
|
||
|
||
Ein Codepfad für beide Betriebsmodi. Im Modus `INTERN` ist die Kunden-Ebene vollständig unsichtbar, ohne dass irgendwo Daten fehlen — dadurch ist der Wechsel später eine Datenänderung und keine Migration.
|
||
|
||
## Aufgaben
|
||
|
||
- [x] `betriebsmodus()` als zentraler, gecachter Zugriff auf `Einstellung.modus`; alle Listen und Formulare fragen ihn ab
|
||
- [x] `kundeErmitteln(eingabe)`: im Modus `INTERN` immer die eigene Organisation, eine mitgeschickte `kundeId` wird **verworfen**
|
||
- [x] Moduswechsel-Service: `INTERN → DIENSTLEISTUNG` jederzeit, die eigene Organisation wird zum ersten Kunden (Name und Kürzel werden abgefragt), alle Daten bleiben daran hängen
|
||
- [x] Moduswechsel-Service: `DIENSTLEISTUNG → INTERN` nur bei genau einem nicht archivierten Kunden, sonst Ablehnung mit Hinweis
|
||
- [x] Beide Richtungen mit Bestätigungsdialog und `Protokoll`-Eintrag, nur `ADMIN`
|
||
- [x] Kundenverwaltung im Modus `DIENSTLEISTUNG`: anlegen, `kuerzel` eindeutig 2–6 Zeichen, archivieren statt löschen, Notizen
|
||
- [x] Navigation und Beschriftungen je Modus: `INTERN` = Übersicht, Läufe, Vorlagen, Mitarbeiter & Geräte, Archiv, Admin; `DIENSTLEISTUNG` = Übersicht, Läufe, Vorlagen, Kunden, Archiv, Admin und „Mitarbeiter“ heißt „Personen“
|
||
- [x] Optionale Kundenbindung über `OIDC_CUSTOMER_GROUP_PREFIX` (z. B. `lz-kunde-MUS`): `TECHNIKER`/`LESER` sehen nur ihre Kunden, `ADMIN`/`REDAKTEUR` alles; ohne die Variable keine Einschränkung
|
||
- [x] Admin → Einstellungen: Modus mit Wechsel-Dialog, Name der eigenen Organisation, globale Namensregeln, Sperr-OU, Anzeige von `APP_URL` für die QR-Codes
|
||
- [x] Screenshots Einstellungen, Wechsel-Dialog, Kundenliste in `docs/screenshots/m3/`
|
||
|
||
## Akzeptanzkriterien
|
||
|
||
- [x] Im Modus `INTERN` erscheint die eigene Organisation in keiner Liste, keinem Filter und auf keinem Etikett. **Eine Ausnahme, bewusst:** auf der Einstellungsseite und im Wechsel-Dialog ist sie sichtbar — dort muss der Admin ihren Namen pflegen und vor dem Wechsel wissen, was aus ihr wird.
|
||
- [x] Test: eine Server Action mit mitgeschickter fremder `kundeId` schreibt im Modus `INTERN` trotzdem auf die eigene Organisation
|
||
- [x] Test: Wechsel `INTERN → DIENSTLEISTUNG` erhält alle Geräte, Mitarbeiter, Abteilungen und Läufe am ersten Kunden
|
||
- [x] Test: Wechsel `DIENSTLEISTUNG → INTERN` wird bei zwei nicht archivierten Kunden mit Hinweis abgelehnt, bei einem gelingt er
|
||
- [x] Test: ein Wechsel schreibt einen `Protokoll`-Eintrag mit Vorher- und Nachher-Zustand
|
||
- [x] Test: `LESER` und `TECHNIKER` dürfen den Modus nicht wechseln
|
||
- [x] Der Modus liegt in der Datenbank und ist ohne Neustart umschaltbar
|
||
|
||
## Beim Bauen aufgefallen
|
||
|
||
- **Die Lesemodelle haben die Mandantensicht nicht angewandt.** `kundenListe()` gab jedem alle
|
||
Kunden zurück, `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 — `CLAUDE.md` verlangt genau das,
|
||
„damit kein Aufrufer sie vergessen kann“.
|
||
- **Mein Kommentar „Alles in einer Transaktion“ war falsch.** Der Protokolleintrag stand hinter
|
||
der Transaktion: schlug er fehl, war der Modus schon gewechselt und der Vorgang stand nirgends.
|
||
Der Review hat es reproduziert. Jetzt läuft alles in einer Transaktion.
|
||
- **Wettlauf beim Moduswechsel.** Prüfung und Vollzug lagen auseinander: zwei gleichzeitige
|
||
Wechsel liefen beide durch, und ein zwischen Prüfung und Vollzug angelegter Kunde führte zu
|
||
Modus `INTERN` bei zwei nicht archivierten Kunden — genau der Zustand, den die Regel verhindern
|
||
soll. Ein `pg_advisory_xact_lock` als erste Anweisung der Transaktion schließt das.
|
||
- **Die Sichtbarkeit steckte in einer zweiten Funktion**, an die jeder künftige Aufrufer selbst
|
||
hätte denken müssen. Sie ist jetzt Teil von `kundeErmitteln` — der einen Stelle, an der die
|
||
Mandantenregel steht. Die Signatur ändert sich damit vor #009, das sie übernimmt.
|
||
- **ASCII-Faltung im falschen Text.** „Kuerzel“, „gehoert“, „Grossbuchstaben“ standen in
|
||
Fehlermeldungen, die der Admin liest, während die Oberfläche sonst „Kürzel“ schreibt. Die Regel
|
||
in `CLAUDE.md` gilt für Bezeichner, nicht für Fließtext — steht jetzt ausdrücklich dort.
|
||
- **Die Erfolgsmeldung nach dem Wechsel war unsichtbar.** Sie wurde im Formularzweig gerendert,
|
||
den der gelungene Wechsel gerade durch die „Rückweg gesperrt“-Box ersetzt.
|
||
- **Der Dialog versprach etwas, das der Code nicht durchsetzt** („Das Verzeichnis bleibt der
|
||
eigenen Organisation vorbehalten"). Eine Zusage auf Vorrat in einem Dialog, dessen ganzer Wert
|
||
darin liegt, nur zu zeigen was wirklich passiert — Satz entfernt.
|
||
- **Der Handy-Screenshot des Wechsel-Dialogs zeigte das Navigationsmenü**, weil
|
||
`getByRole("group").first()` bei 390 px das erste `details` der Seite trifft und das die
|
||
Navigation ist.
|
||
- **Der Screenshot-Lauf im Modus Dienstleistung stürzte ab** — und das war korrektes Verhalten:
|
||
mit zwei nicht archivierten Kunden gibt es keinen Dialog zum Aufklappen. Daraus ist ein eigener
|
||
Screenshot geworden, der die Ablehnung zeigt.
|