# #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.