feat(mitarbeiter): Vorgesetzter auch fuer Personen ohne Verzeichnis #43

Merged
florian.wilde merged 1 commits from feat/31-vorgesetzter-manuell into main 2026-10-04 15:10:35 +00:00
Owner

Eine Person, die nicht aus dem Verzeichnis kommt, konnte keinen Vorgesetzten haben. {{vorgesetzter_sam}} blieb dort immer leer — genau im Onboarding-Befehl, wo er gebraucht wird.

Die Entscheidung

Das Issue stellte zwei Wege zur Wahl. Gewählt: eine eigene Selbstbeziehung vorgesetzterId — und die befürchteten „zwei Wege zur selben Auskunft" entstehen nicht, weil der Abgleich sie mitschreibt. vorgesetzterExterneId ist damit wie abteilung nur noch der rohe Wert des Verzeichnisses; gelesen wird allein vorgesetzterId, an beiden Stellen.

Die Alternative hätte manuellen Personen eine erfundene externeId geben müssen. externeId heißt „objectGUID aus dem Verzeichnis" — das wäre eine Lüge im Datenmodell gewesen, und @@unique([quelleId, externeId]) hätte danach auch manuelle Zeilen eingeschränkt.

Drei Grenzen an drei Stellen

  • Mandantengrenze: zusammengesetzter Fremdschlüssel (vorgesetzterId, kundeId) → (id, kundeId), wie bei Lauf und Geraet. Der Dienst prüft dasselbe noch einmal, nur mit besserer Meldung.
  • Kürzeste Schleife: ein CHECK vorgesetzterId <> id. Niemand ist sein eigener Vorgesetzter.
  • Längere Schleifen: der Dienst läuft die Kette hinauf, mit Obergrenze — wäre die Kette schon beschädigt, liefe die Suche sonst ewig.

Was das Verzeichnis darf und was nicht

Liefert es einen manager, bestimmt es — beide Felder. Liefert es keinen, bleibt eine von Hand gesetzte Zuordnung stehen: geleert wird nur, was das Verzeichnis selbst gesetzt hatte (erkennbar daran, dass dort eine Kennung stand). Gegenprobe gefahren: ohne diese Einschränkung wird der Test rot.

Der Umstiegspfad

Die Migration führt bestehende Daten nach. Ohne sie wäre die Auskunft nach dem Einspielen bei jeder Verzeichnis-Person leer gewesen, bis der nächste Abgleich läuft — und zwar still. Dieselbe Falle wie der Seed-Schlüssel in #32: ein Umstieg, der die vorhandenen Daten nicht mitnimmt. An einem nachgebauten Vor-Zustand in der Entwicklungsdatenbank gefahren: m-probe trug danach die Zuordnung auf Sonja Brand.

Seed

Petra Klein ist jetzt Vorgesetzte von Maria Huber — die Zuordnung aus dem Auftrag, die sich vorher nirgends speichern ließ. Gesetzt wird sie nur für Personen, die derselbe Lauf angelegt hat: eine leere Zuordnung sagt nicht, ob sie noch nie gesetzt oder im Betrieb entfernt wurde, und ein zweiter Seed-Lauf darf nichts zurückdrehen.

Oberfläche

Auswahlfeld und Knopf auf der Personenseite, kein eigener Bearbeiten-Modus. Die leere Option heißt ausdrücklich „niemand" und nicht „—": sie ist eine Angabe. Angeboten werden nur aktive Personen desselben Kunden, ohne die Person selbst — eine Auswahlliste mit unmöglichen Einträgen ist eine Falle.

Im gebauten Stand durchgeklickt, nicht nur gebaut: anmelden, Person öffnen, speichern, neu laden (Wert steht), Kopfzeile zeigt den Namen, eigene Id wird nicht angeboten, entfernen geht, und bei 390 px kein horizontales Scrollen (scrollWidth 390 = clientWidth 390). Das Protokoll trug danach beide Aktionen.

Tests

Dreizehn neue (1156 statt 1143), darunter alle drei Kriterien des Issues, beide Schleifenlängen, die Mandantengrenze auf Dienst- und Datenbankebene, und der Seed-Fall in beide Richtungen.

Alle Tore grün: lint, typecheck, format:pruefen, pruefe:doku, 1156 Vitest-Tests in 52 Dateien.

Closes #31

🤖 Generated with Claude Code

Eine Person, die nicht aus dem Verzeichnis kommt, konnte keinen Vorgesetzten haben. `{{vorgesetzter_sam}}` blieb dort immer leer — genau im Onboarding-Befehl, wo er gebraucht wird. ## Die Entscheidung Das Issue stellte zwei Wege zur Wahl. Gewählt: **eine eigene Selbstbeziehung `vorgesetzterId`** — und die befürchteten „zwei Wege zur selben Auskunft" entstehen nicht, weil **der Abgleich sie mitschreibt**. `vorgesetzterExterneId` ist damit wie `abteilung` nur noch der rohe Wert des Verzeichnisses; gelesen wird allein `vorgesetzterId`, an beiden Stellen. Die Alternative hätte manuellen Personen eine erfundene `externeId` geben müssen. `externeId` heißt „objectGUID aus dem Verzeichnis" — das wäre eine Lüge im Datenmodell gewesen, und `@@unique([quelleId, externeId])` hätte danach auch manuelle Zeilen eingeschränkt. ## Drei Grenzen an drei Stellen - **Mandantengrenze:** zusammengesetzter Fremdschlüssel `(vorgesetzterId, kundeId) → (id, kundeId)`, wie bei `Lauf` und `Geraet`. Der Dienst prüft dasselbe noch einmal, nur mit besserer Meldung. - **Kürzeste Schleife:** ein CHECK `vorgesetzterId <> id`. Niemand ist sein eigener Vorgesetzter. - **Längere Schleifen:** der Dienst läuft die Kette hinauf, mit Obergrenze — wäre die Kette schon beschädigt, liefe die Suche sonst ewig. ## Was das Verzeichnis darf und was nicht Liefert es einen `manager`, bestimmt es — beide Felder. Liefert es **keinen**, bleibt eine von Hand gesetzte Zuordnung stehen: geleert wird nur, was das Verzeichnis selbst gesetzt hatte (erkennbar daran, dass dort eine Kennung stand). **Gegenprobe gefahren:** ohne diese Einschränkung wird der Test rot. ## Der Umstiegspfad **Die Migration führt bestehende Daten nach.** Ohne sie wäre die Auskunft nach dem Einspielen bei *jeder* Verzeichnis-Person leer gewesen, bis der nächste Abgleich läuft — und zwar still. Dieselbe Falle wie der Seed-Schlüssel in #32: ein Umstieg, der die vorhandenen Daten nicht mitnimmt. An einem nachgebauten Vor-Zustand in der Entwicklungsdatenbank gefahren: `m-probe` trug danach die Zuordnung auf Sonja Brand. ## Seed Petra Klein ist jetzt Vorgesetzte von Maria Huber — die Zuordnung aus dem Auftrag, die sich vorher nirgends speichern ließ. Gesetzt wird sie nur für Personen, die **derselbe Lauf** angelegt hat: eine leere Zuordnung sagt nicht, ob sie noch nie gesetzt oder im Betrieb entfernt wurde, und ein zweiter Seed-Lauf darf nichts zurückdrehen. ## Oberfläche Auswahlfeld und Knopf auf der Personenseite, kein eigener Bearbeiten-Modus. Die leere Option heißt ausdrücklich **„niemand"** und nicht „—": sie ist eine Angabe. Angeboten werden nur aktive Personen desselben Kunden, ohne die Person selbst — eine Auswahlliste mit unmöglichen Einträgen ist eine Falle. **Im gebauten Stand durchgeklickt**, nicht nur gebaut: anmelden, Person öffnen, speichern, **neu laden** (Wert steht), Kopfzeile zeigt den Namen, eigene Id wird nicht angeboten, entfernen geht, und bei 390 px kein horizontales Scrollen (`scrollWidth 390 = clientWidth 390`). Das Protokoll trug danach beide Aktionen. ## Tests Dreizehn neue (1156 statt 1143), darunter alle drei Kriterien des Issues, beide Schleifenlängen, die Mandantengrenze auf Dienst- **und** Datenbankebene, und der Seed-Fall in beide Richtungen. Alle Tore grün: `lint`, `typecheck`, `format:pruefen`, `pruefe:doku`, **1156 Vitest-Tests** in 52 Dateien. Closes #31 🤖 Generated with [Claude Code](https://claude.com/claude-code)
florian.wilde added 1 commit 2026-10-04 15:10:33 +00:00
feat(mitarbeiter): Vorgesetzter auch fuer Personen ohne Verzeichnis
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
02ca74e661
Die Zuordnung lief allein ueber vorgesetzterExterneId, und die ist nur
innerhalb einer Verzeichnisquelle eindeutig: eine von Hand angelegte Person
konnte keinen Vorgesetzten haben, und {{vorgesetzter_sam}} blieb im
Onboarding-Befehl leer -- genau dort, wo er gebraucht wird.

Entschieden fuer eine eigene Selbstbeziehung vorgesetzterId. Die befuerchteten
"zwei Wege zur selben Auskunft" entstehen nicht, weil der Abgleich sie
mitschreibt: vorgesetzterExterneId ist jetzt wie abteilung nur der rohe Wert
des Verzeichnisses, gelesen wird allein vorgesetzterId. Die Alternative haette
manuellen Personen eine erfundene externeId geben muessen, und externeId heisst
"objectGUID aus dem Verzeichnis".

Liefert das Verzeichnis keinen manager, bleibt eine von Hand gesetzte Zuordnung
stehen -- geleert wird nur, was das Verzeichnis selbst gesetzt hatte.

Die Migration fuehrt bestehende Daten nach. Ohne das waere die Auskunft nach
dem Einspielen bei jeder Verzeichnis-Person still leer gewesen, bis der
naechste Abgleich laeuft. An einem nachgebauten Vor-Zustand gefahren.

Drei Grenzen, drei Stellen: der zusammengesetzte Fremdschluessel haelt die
Mandantengrenze, ein CHECK die kuerzeste Schleife, und der Dienst laeuft die
Kette hinauf fuer laengere.

Im gebauten Stand durchgeklickt: speichern, neu laden, Wert steht, entfernen,
390 px ohne horizontales Scrollen. Gegenprobe am Abgleich gefahren.

Closes #31
florian.wilde merged commit 0060a55387 into main 2026-10-04 15:10:35 +00:00
florian.wilde deleted branch feat/31-vorgesetzter-manuell 2026-10-04 15:10:35 +00:00
Sign in to join this conversation.