feat(stammdaten): Mitarbeiter, Abteilungen und Geraete (#25)
This commit was merged in pull request #25.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# #011 M6 – Mitarbeiter, Abteilungen, Geräte
|
||||
|
||||
**Status:** offen
|
||||
**Status:** erledigt
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend
|
||||
**Geschätzt:** 9 h **Tatsächlich:** –
|
||||
**Geschätzt:** 9 h **Tatsächlich:** 2 h 15 min
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -11,23 +11,69 @@ Die Stammdaten, an denen die Historie hängt. Mitarbeiter und Geräte werden nie
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Mitarbeiterliste: Suche über Anzeigename, Mail, UPN und `sam`, Filter nach Abteilung und Aktiv-Status
|
||||
- [ ] Mitarbeitersuche als wiederverwendbare Komponente: standardmäßig nur aktive, Umschalter „auch deaktivierte“ fürs Offboarding
|
||||
- [ ] Mitarbeiterseite: Stammdaten, Abteilung, Vorgesetzter, seine Geräte, alle Läufe chronologisch (Onboarding, Übergaben, Offboarding)
|
||||
- [ ] Mitarbeiter manuell anlegen und bearbeiten (`herkunft = MANUELL`); synchronisierte Felder sind bei `herkunft = ABGLEICH` gesperrt
|
||||
- [ ] Deaktivieren statt löschen (`aktiv = false`)
|
||||
- [ ] Abteilungen: Name (eindeutig je Kunde), Ziel-OU, Standardgruppen (`String[]`), Mail-Domain, Notizen
|
||||
- [ ] Geräte: Stammdaten nach Vorgabe, Pflichtregel „`hostname` oder `bezeichnung` muss gesetzt sein“, Besitzer optional
|
||||
- [ ] Geräteliste mit Suche über Hostname, Bezeichnung, Seriennummer und `tagCode`, Filter nach Typ
|
||||
- [ ] CSV-Import für Geräte (`hostname`, `label`, `serialNumber`, `inventoryNumber`, `type`, `model`), `tagCode` wird beim Import erzeugt; eigener `src/lib/csv.ts`
|
||||
- [ ] Archivieren statt Löschen, sobald Läufe an einem Gerät oder einer Abteilung hängen
|
||||
- [ ] Screenshots Mitarbeiterliste, Mitarbeiterseite, Geräteliste, Abteilungen in `docs/screenshots/m6/`
|
||||
- [x] Mitarbeiterliste: Suche über Anzeigename, Mail, UPN und `sam`, Filter nach Abteilung und Aktiv-Status
|
||||
- [x] Mitarbeitersuche als wiederverwendbare Komponente: standardmäßig nur aktive, Umschalter „auch deaktivierte“ fürs Offboarding
|
||||
- [x] Mitarbeiterseite: Stammdaten, Abteilung, Vorgesetzter, seine Geräte, alle Läufe chronologisch (Onboarding, Übergaben, Offboarding)
|
||||
- [x] Mitarbeiter manuell anlegen und bearbeiten (`herkunft = MANUELL`); synchronisierte Felder sind bei `herkunft = ABGLEICH` gesperrt
|
||||
- [x] Deaktivieren statt löschen (`aktiv = false`)
|
||||
- [x] Abteilungen: Name (eindeutig je Kunde), Ziel-OU, Standardgruppen (`String[]`), Mail-Domain, Notizen
|
||||
- [x] Geräte: Stammdaten nach Vorgabe, Pflichtregel „`hostname` oder `bezeichnung` muss gesetzt sein“, Besitzer optional
|
||||
- [x] Geräteliste mit Suche über Hostname, Bezeichnung, Seriennummer und `tagCode`, Filter nach Typ
|
||||
- [x] CSV-Import für Geräte (`hostname`, `label`, `serialNumber`, `inventoryNumber`, `type`, `model`), `tagCode` wird beim Import erzeugt; eigener `src/lib/csv.ts`
|
||||
- [x] Archivieren statt Löschen, sobald Läufe an einem Gerät oder einer Abteilung hängen
|
||||
- [x] Screenshots Mitarbeiterliste, Mitarbeiterseite, Geräteliste, Abteilungen in `docs/screenshots/m6/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: Mitarbeitersuche liefert standardmäßig nur `aktiv = true`; mit Umschalter auch deaktivierte
|
||||
- [ ] Test: ein Gerät ohne `hostname` und ohne `bezeichnung` wird abgelehnt
|
||||
- [ ] Test: ein Gerät mit Läufen lässt sich nicht löschen, nur archivieren
|
||||
- [ ] Test: `tagCode` eines bestehenden Geräts lässt sich nicht ändern
|
||||
- [ ] Test: CSV-Import erzeugt je Zeile einen `tagCode`, meldet fehlerhafte Zeilen einzeln und bricht nicht ab
|
||||
- [ ] Test: bei `herkunft = ABGLEICH` lehnt der Service Änderungen an synchronisierten Feldern ab
|
||||
- [x] Test: Mitarbeitersuche liefert standardmäßig nur `aktiv = true`; mit Umschalter auch deaktivierte
|
||||
- [x] Test: ein Gerät ohne `hostname` und ohne `bezeichnung` wird abgelehnt
|
||||
- [x] Test: ein Gerät mit Läufen lässt sich nicht löschen, nur archivieren
|
||||
- [x] Test: `tagCode` eines bestehenden Geräts lässt sich nicht ändern
|
||||
- [x] Test: CSV-Import erzeugt je Zeile einen `tagCode`, meldet fehlerhafte Zeilen einzeln und bricht nicht ab
|
||||
- [x] Test: bei `herkunft = ABGLEICH` lehnt der Service Änderungen an synchronisierten Feldern ab
|
||||
|
||||
## Beim Bauen aufgefallen
|
||||
|
||||
- **Der CSV-Import hat Zeilen still verloren und Erfolg gemeldet.** Ein Anführungszeichen, das in
|
||||
einer Zelle beginnt und nicht schließt, verschluckte alle Folgezeilen bis zum nächsten `"` — der
|
||||
Import sagte „3 von 3 Zeilen angelegt“, zwei Geräte fehlten, und in einer Bezeichnung stand der
|
||||
Text der verschluckten Zeilen. Umgekehrt starb der ganze Import an einer Datei, in der nirgends
|
||||
mehr ein `"` folgte. Beides ist genau das Gegenteil des Akzeptanzkriteriums. Der Zerleger setzt
|
||||
jetzt am Zeilenende neu auf: **ein Wert reicht standardmäßig nicht über die Zeile hinaus**. Das
|
||||
Format erlaubt es, für Hostnamen und Seriennummern ist ein Umbruch aber praktisch immer ein
|
||||
vergessenes Anführungszeichen — und mit dieser Grenze endet der Schaden an der Zeile, in der er
|
||||
beginnt. Wer eine selbst erzeugte Datei zurückliest, schaltet `werteUeberZeilen` ein.
|
||||
- **`ungepflegteAbteilungen` kannte die Kundengrenze nicht.** Gruppiert wurde nach dem Namen
|
||||
allein, also galt „Vertrieb“ als gepflegt, sobald **irgendein** Kunde eine Abteilung dieses
|
||||
Namens hatte. Beim Kunden ohne sie blieben `{{ou}}` und `{{gruppen}}` im Lauf leer, ohne dass die
|
||||
Seite es je sagte. Die Funktion direkt darüber machte es seit jeher richtig — zwei Funktionen im
|
||||
selben Modul, die sich widersprachen.
|
||||
- **Die Besitzerauswahl bot Personen fremder Kunden an.** Die Liste wurde einmal für die ganze
|
||||
Seite geholt und in jede Karte gegeben. Der Dienst wies die Wahl ab — aber erst nach dem Klick,
|
||||
und die Oberfläche hatte etwas angeboten, das sie nicht einlösen konnte.
|
||||
- **Ein Techniker konnte ein Gerät endgültig löschen.** Abteilungen und Mitarbeiter haben gar
|
||||
keinen Löschweg, Geräte schon — und der Tag-Code ist danach für immer verbrannt. Löschen verlangt
|
||||
jetzt `verwaltung`; die Ausnahme steht ausdrücklich in `CLAUDE.md`.
|
||||
- **Derselbe Kunde wurde zweimal verschieden dargestellt.** Die Mitarbeiterliste blendete die
|
||||
eigene Organisation immer aus, die Geräteliste zeigte sie immer an. Im Modus Dienstleistung hieß
|
||||
„keine Kundenangabe“ damit nicht „kein Kunde“, sondern „ausgerechnet dieser eine“. Jetzt liefern
|
||||
beide Lesemodelle den Namen, und **die Seite** entscheidet anhand des Betriebsmodus, ob sie ihn
|
||||
zeigt.
|
||||
- **Ein Kommentar hat wieder eine Zusage gemacht, die der Code nicht hielt** („zwei verschiedene
|
||||
Gründe, aber nicht dieselbe Meldung“) — gerendert wurde nur eine, und wer die Rolle nicht hatte,
|
||||
sah gesperrte Felder ohne jede Erklärung. Vierter Meilenstein in Folge mit genau dieser Sorte
|
||||
Fehler.
|
||||
- **`FELD` stand neunmal wortgleich in neun Dateien.** Jetzt einmal in `src/lib/formular.ts`.
|
||||
- **Die E2E-Tests haben ihre Daten liegen gelassen.** Sie können nichts löschen — die Anwendung
|
||||
kennt bewusst keinen Löschweg für Personen und Abteilungen —, also räumen sie am Ende über die
|
||||
Datenbank auf. Ohne das sähe jede Datenbank, gegen die sie laufen, nach ein paar Durchläufen aus
|
||||
wie ein Messie-Keller.
|
||||
- **Die Mitarbeitersuche ist ein eigenes Bauteil** (`src/components/mitarbeiter-suche.tsx`), weil
|
||||
#013 sie beim Starten eines Laufs und #014 beim Zuweisen eines Geräts wieder braucht.
|
||||
|
||||
## Offen geblieben
|
||||
|
||||
- **Ab 501 aktiven Personen** lässt sich ein Besitzer nicht mehr über das Auswahlfeld zuordnen. Die
|
||||
Grenze schützt davor, ein Verzeichnis mit zehntausend Personen in ein `<select>` zu gießen; die
|
||||
Zuweisung über die Geräteseite mit Suche kommt mit #014. Bis dahin sagt das Formular es
|
||||
ausdrücklich, statt einen Weg zu versprechen, den es nicht gibt.
|
||||
|
||||
Reference in New Issue
Block a user