feat(vorlagen): Versionierung, Unveraenderlichkeit und JSON-Austausch
Eine veroeffentlichte Vorlagen-Version ist fuer immer unveraenderlich — daran haengt, dass ein archivierter Lauf in zehn Jahren noch genau die Schritte zeigt, mit denen er gestartet wurde. Die Regel steht deshalb zweimal: im Service, der die verstaendliche Meldung gibt, und in der Datenbank, die die Garantie gibt. Vier Trigger fangen Schreibzugriffe ab, die nicht durch den Service kommen — Migrationen, psql, der Seed und ab Phase 4 die Connectoren — und versperren den Rueckweg nach ENTWURF, der alles andere ausgehebelt haette. - Service: anlegen, Kopfdaten bearbeiten, Entwurf als tiefe Kopie der letzten veroeffentlichten Version, speichern, veroeffentlichen, verwerfen, duplizieren, archivieren - Hoechstens ein Entwurf je Vorlage, durchgesetzt vom partiellen Index; zwei gleichzeitige Klicks auf „Bearbeiten“ bekommen denselben Entwurf statt eines Fehlers - Optimistisches Sperren ueber geaendertAm: wer auf einem ueberholten Stand speichert, wird abgewiesen statt die Arbeit des anderen spurlos zu ersetzen - JSON-Export und -Import samt Format-Dokumentation; der Export ist nachsichtig, der Import streng - src/lib/validierung.ts als einzige Zod-Quelle mit deutschen Meldungen, per ESLint-Regel erzwungen Der unabhaengige Review fand drei Blocker, alle an der Mandantengrenze: der Suchfilter brachte ein zweites OR mit und ueberschrieb die Kundenbedingung, Historie und Aufbau einer Version filterten gar nicht. Zu queries/vorlagen.ts gab es keinen Test — jetzt gibt es tests/vorlagen-sicht.test.ts. 428 Tests gruen (vorher 336). Schliesst #009 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ecc3c579df
commit
abacad1cb7
+6
-6
@@ -33,7 +33,7 @@ Punkt ist entweder abgehakt oder hat ein Folge-Issue — nichts bleibt stillschw
|
||||
|
||||
## Vorlagen
|
||||
|
||||
- [ ] Veröffentlichte Version ist unveränderlich; „Bearbeiten" erzeugt eine tiefe Kopie als Entwurf
|
||||
- [ ] Veröffentlichte Version ist unveränderlich; „Bearbeiten“ erzeugt eine tiefe Kopie als Entwurf
|
||||
- [ ] Höchstens ein Entwurf je Vorlage
|
||||
- [ ] Veröffentlichen einer neuen Version verändert bestehende Läufe nicht
|
||||
- [ ] Export und anschließender Import ergeben eine inhaltsgleiche Vorlage
|
||||
@@ -44,7 +44,7 @@ Punkt ist entweder abgehakt oder hat ein Folge-Issue — nichts bleibt stillschw
|
||||
- [ ] Lauf ohne Mitarbeiter und ohne Gerät lässt sich starten, abschließen und archivieren
|
||||
- [ ] Abschließen scheitert, solange ein Pflichtschritt offen ist
|
||||
- [ ] `NICHT_ZUTREFFEND` und `FEHLGESCHLAGEN` verlangen eine Notiz
|
||||
- [ ] „Abschnitt als nicht zutreffend markieren" setzt alle Schritte mit einer Notiz
|
||||
- [ ] „Abschnitt als nicht zutreffend markieren“ setzt alle Schritte mit einer Notiz
|
||||
- [ ] Archiv-Schreibschutz greift für Kopfdaten, Schrittergebnisse und Kommentare
|
||||
- [ ] Nur `ADMIN` holt einen archivierten Lauf zurück, mit `Protokoll`-Eintrag
|
||||
- [ ] Im Archiv wird über Titel, Notizen, Werte und Kommentare gefunden
|
||||
@@ -55,7 +55,7 @@ Punkt ist entweder abgehakt oder hat ein Folge-Issue — nichts bleibt stillschw
|
||||
- [ ] Mitarbeitersuche liefert standardmäßig nur aktive, Umschalter zeigt auch deaktivierte
|
||||
- [ ] Ein von der Geräteseite gestarteter Lauf hat den Besitzer vorbelegt
|
||||
- [ ] `/t/LZ-7K3F9Q` ohne Anmeldung führt nach dem Login auf die Geräteseite
|
||||
- [ ] „Vorlage zuweisen" erzeugt einen `OFFEN`-Lauf, der unter „Zugewiesen" erscheint; „Starten" setzt `IN_ARBEIT`
|
||||
- [ ] „Vorlage zuweisen“ erzeugt einen `OFFEN`-Lauf, der unter „Zugewiesen“ erscheint; „Starten“ setzt `IN_ARBEIT`
|
||||
- [ ] Unregistrierter Tag → Registrierformular → Geräteseite; unbekannter Tag → Hinweis
|
||||
- [ ] `tagCode`: richtiges Alphabet, eindeutig, unveränderlich, nie wiederverwendet
|
||||
- [ ] Etikettendruck in beiden Layouts sauber; QR maschinell zurücklesbar
|
||||
@@ -63,12 +63,12 @@ Punkt ist entweder abgehakt oder hat ein Folge-Issue — nichts bleibt stillschw
|
||||
## Platzhalter und Befehle
|
||||
|
||||
- [ ] Onboarding: Vorname und Nachname eingeben → UPN, `sam` und Mail werden vorgeschlagen
|
||||
- [ ] Abteilung „Buchhaltung" wählen → `-Path "OU=Buchhaltung,…"` und Gruppen `G-Buchhaltung, G-Alle`
|
||||
- [ ] Abteilung „Buchhaltung“ wählen → `-Path "OU=Buchhaltung,…"` und Gruppen `G-Buchhaltung, G-Alle`
|
||||
- [ ] Kopieren liefert exakt diesen Text, zeichengenau
|
||||
- [ ] „Jörg Müller-Lüdenscheid" ergibt gültige Vorschläge (`joerg.mueller-luedenscheid@…`, `jmuellerluedensch`)
|
||||
- [ ] „Jörg Müller-Lüdenscheid“ ergibt gültige Vorschläge (`joerg.mueller-luedenscheid@…`, `jmuellerluedensch`)
|
||||
- [ ] `{{generiertes_passwort}}` erscheint im Befehl, steht aber nicht in Datenbank, Druckansicht oder `Protokoll`
|
||||
- [ ] Werte mit Anführungszeichen werden für die jeweilige Shell korrekt maskiert
|
||||
- [ ] „Alle Befehle als Skript kopieren" liefert die Schritte in richtiger Reihenfolge
|
||||
- [ ] „Alle Befehle als Skript kopieren“ liefert die Schritte in richtiger Reihenfolge
|
||||
- [ ] Unbekannte Platzhalter bleiben stehen; ersetzte Werte sind HTML-escaped
|
||||
|
||||
## Verzeichnis (LDAP)
|
||||
|
||||
+9
-2
@@ -42,7 +42,7 @@ Der Auftrag nannte Auth.js. Beim Prüfen der Lage am 2026-09-24:
|
||||
|
||||
- `next-auth` v5 steht auf `5.0.0-beta.32`, seit Jahren ohne GA
|
||||
- authjs.dev führt das Projekt im Wartungsmodus („The Auth.js project is now part of Better
|
||||
Auth"), die Maintainer schreiben „no immediate plans for v5"
|
||||
Auth"), die Maintainer schreiben „no immediate plans for v5“
|
||||
- `@auth/prisma-adapter` deklariert `@prisma/client` nur bis `>=6` — wir fahren Prisma 7
|
||||
|
||||
Entschieden wurde deshalb der Eigenbau mit stabilen Bausteinen, wie in ProxLedger:
|
||||
@@ -59,7 +59,7 @@ Cookie abgewiesen.
|
||||
|
||||
Zwei Regeln tragen die Nachvollziehbarkeit:
|
||||
|
||||
1. **Eine veröffentlichte `VorlagenVersion` wird nie verändert.** „Bearbeiten" erzeugt eine tiefe
|
||||
1. **Eine veröffentlichte `VorlagenVersion` wird nie verändert.** „Bearbeiten“ erzeugt eine tiefe
|
||||
Kopie als Entwurf. Ein Lauf auf Version 1 zeigt für immer die Schritte aus Version 1, auch
|
||||
wenn längst Version 5 veröffentlicht ist.
|
||||
2. **Ein archivierter Lauf ist schreibgeschützt.** Die Sperre sitzt in genau einer Funktion,
|
||||
@@ -68,6 +68,13 @@ Zwei Regeln tragen die Nachvollziehbarkeit:
|
||||
|
||||
Beides ist durch Tests abgedeckt, nicht durch Disziplin.
|
||||
|
||||
Der Aufbau einer Version wird **als Ganzes** gespeichert, nicht Schritt für Schritt: der Editor
|
||||
hält den Baum im Speicher und sortiert per Drag & Drop, ein Teil-Update wäre dort ständig mit dem
|
||||
Sortieren im Streit. Dasselbe Zod-Schema (`src/lib/vorlagen-format.ts`) beschreibt den Entwurf im
|
||||
Editor, den JSON-Export und den Import — drei getrennte Schemata würden auseinanderlaufen, und der
|
||||
Import wäre der Ort, an dem das erst auffiele. Das Format ist in
|
||||
[`docs/vorlagen-format.md`](vorlagen-format.md) beschrieben.
|
||||
|
||||
## 5. Der Weg zu Phase 4 (Aktionen)
|
||||
|
||||
Die in den Vorlagen vorbereiteten PowerShell-Befehle sind bewusst die **Vorstufe ausführbarer
|
||||
|
||||
+72
-4
@@ -11,11 +11,12 @@ als Unterpunkte mit Aufgabe und Dauer.
|
||||
| #006 | M1 – Datenmodell | 1 h 10 min |
|
||||
| #007 | M2 – Auth, Rollen, Benutzer | 1 h 35 min |
|
||||
| #008 | M3 – Betriebsmodus und Kunden | 1 h 20 min |
|
||||
| | **Summe** | **5 h 30 min** |
|
||||
| #009 | M4 – Vorlagen-Versionierung im Service | 1 h 20 min |
|
||||
| | **Summe** | **6 h 50 min** |
|
||||
|
||||
| Meilenstein | Tatsächlich | Status |
|
||||
|---|---|---|
|
||||
| v0.1.0 | 5 h 30 min | in Arbeit |
|
||||
| v0.1.0 | 6 h 50 min | in Arbeit |
|
||||
|
||||
---
|
||||
|
||||
@@ -43,7 +44,7 @@ als Unterpunkte mit Aufgabe und Dauer.
|
||||
- **Deutsch im Code, auch für Bezeichner.** Der Auftragstext verlangte Englisch; Florian hat sich
|
||||
für Einheitlichkeit mit ProxLedger entschieden. Verbindliche Namensabbildung in `CLAUDE.md`.
|
||||
- **Kein Auth.js.** v5 steht seit Jahren auf Beta (`5.0.0-beta.32`), authjs.dev führt das Projekt
|
||||
im Wartungsmodus („now part of Better Auth"), die Maintainer nennen „no immediate plans for v5",
|
||||
im Wartungsmodus („now part of Better Auth“), die Maintainer nennen „no immediate plans for v5“,
|
||||
und `@auth/prisma-adapter` deklariert Prisma nur bis 6 — wir fahren Prisma 7. Stattdessen
|
||||
`openid-client` + `jose` wie in ProxLedger. Dieselbe Überlegung wie dort, nur ein Jahr später
|
||||
noch deutlicher.
|
||||
@@ -246,7 +247,7 @@ als Unterpunkte mit Aufgabe und Dauer.
|
||||
- **Die Sichtbarkeit gehört in `kundeErmitteln`,** nicht in eine zweite Funktion daneben. Sonst
|
||||
müsste jeder künftige Service daran denken — und #009 ff. übernehmen diese Signatur.
|
||||
- **Lesemodelle filtern selbst.** Sie verlangen den Benutzer, statt sich auf den Aufrufer zu
|
||||
verlassen. `undefined` heißt „keine Einschränkung", `[]` heißt „sieht nichts"; die Umsetzung
|
||||
verlassen. `undefined` heißt „keine Einschränkung“, `[]` heißt „sieht nichts“; die Umsetzung
|
||||
steht an einer Stelle, und beide Fälle haben einen Test.
|
||||
- **`pg_advisory_xact_lock` statt Serializable.** Der Wechsel ist selten und kurz; ein Schloss auf
|
||||
Transaktionsebene ist verständlicher als eine Isolationsstufe, die man beim Lesen übersieht, und
|
||||
@@ -271,3 +272,70 @@ als Unterpunkte mit Aufgabe und Dauer.
|
||||
|
||||
- #009 Vorlagen-Versionierung: Veröffentlichen, tiefe Kopie als Entwurf, höchstens ein Entwurf,
|
||||
Unveränderlichkeit, JSON-Export und -Import.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-25 · Sitzung 5 · 07:00–08:20
|
||||
|
||||
**Issue:** #009 (M4)
|
||||
**Version:** 0.1.0
|
||||
|
||||
### Erledigt
|
||||
|
||||
- `src/server/services/vorlagen.ts`: Anlegen, Kopfdaten bearbeiten, Entwurf holen (tiefe Kopie),
|
||||
Speichern, Veröffentlichen, Verwerfen, Duplizieren, Archivieren
|
||||
- `src/server/services/vorlagen-austausch.ts` mit `docs/vorlagen-format.md`: ein Zod-Schema für
|
||||
Editor, Export und Import — eine Vorlage zieht als Datei zwischen zwei Installationen um
|
||||
- `src/lib/validierung.ts` als einzige Quelle für Zod samt deutscher Meldungen, mit ESLint-Regel
|
||||
- Migration `vorlagen_unveraenderlich`: vier Trigger, die die Unveränderlichkeit in der Datenbank
|
||||
durchsetzen — nicht nur im Service
|
||||
- Unabhängiger Review: drei Blocker (alle an der Mandantengrenze), acht weitere Befunde, alle
|
||||
abgearbeitet
|
||||
- **428 Tests** grün (vorher 336), davon neu: Mandantensicht, Datenbankregeln, Gleichzeitigkeit,
|
||||
Austausch mit unsauberen Daten
|
||||
|
||||
### Entscheidungen
|
||||
|
||||
- **Die Regel steht zweimal: im Service und in der Datenbank.** Der Service gibt die verständliche
|
||||
Meldung, die Datenbank die Garantie. Alles, was nicht durch den Service geht — Migrationen,
|
||||
`psql`, der Seed, ab Phase 4 die Connectoren —, läuft sonst an der einen Regel vorbei, die das
|
||||
Archiv überhaupt erst etwas wert macht. Die doppelte Haltung kostet einen Trigger und spart die
|
||||
Annahme, dass jeder künftige Schreibweg daran denkt.
|
||||
- **Ein Entwurf je Vorlage kommt vom partiellen Index**, nicht von einer Prüfung davor. Zwei
|
||||
gleichzeitige Klicks auf „Bearbeiten“ laufen damit unvermeidlich in `P2002` — und genau dort
|
||||
wiederholt der Service und gibt beiden denselben Entwurf zurück, statt einen Fehler zu zeigen,
|
||||
den niemand versteht.
|
||||
- **Optimistisches Sperren über `geaendertAm`,** nicht über eine Zählerspalte. Der Zeitstempel ist
|
||||
ohnehin da, und `updateMany` mit Bedingung erledigt den Vergleich in derselben Anweisung.
|
||||
- **Unbekannte `konfig`-Schlüssel überleben, unbekannte Werte bekannter Schlüssel nicht.** Ein
|
||||
neues Feld aus einer späteren Fassung darf einen Umzug nicht verhindern; eine unbekannte `shell`
|
||||
aber entscheidet darüber, wie ein Wert maskiert wird — sie stillschweigend zu verwerfen hieße,
|
||||
einen bash-Befehl nach PowerShell-Regeln zu maskieren, und das kann auf einem Server Daten
|
||||
kosten. Also laut abweisen, mit Pfad und erlaubten Werten.
|
||||
- **Der Export ist nachsichtig, der Import streng.** Herausgeben muss man auch, was nicht ins
|
||||
Schema passt, sonst verliert die Datei Inhalt. Annehmen darf man es nicht.
|
||||
- **`tests/datenbankregeln.test.ts` als Wächter.** Prisma erzeugt für jede künftige Migration
|
||||
Löschbefehle gegen die handgeschriebenen Objekte. Der Test zählt sie namentlich auf, damit ein
|
||||
übersehener `DROP` die Suite rot macht statt still die Mandantengrenze zu entfernen.
|
||||
|
||||
### Probleme
|
||||
|
||||
- **Die drei Blocker lagen alle an derselben Stelle: den Lesemodellen.** Der Suchfilter brachte
|
||||
einen zweiten `OR`-Schlüssel mit und überschrieb die Kundenbedingung — ein Buchstabe im Suchfeld
|
||||
genügte, um einem gebundenen Techniker alle Vorlagen aller Kunden zu zeigen. Historie und Aufbau
|
||||
einer Version filterten gar nicht. Zu `queries/vorlagen.ts` gab es keinen Test; die Sicht in #008
|
||||
abzusichern und in #009 nur den Service zu testen war der Fehler.
|
||||
- **`Prisma.validator`-Objekte mit zwei gleichen Schlüsseln sind kein Typfehler.** Ein
|
||||
zusammengebautes `where` mit `OR` aus zwei Quellen fällt weder ESLint noch `tsc` auf. Deshalb
|
||||
liegt die Mandantenbedingung jetzt in `AND`, wo ein zweiter Filter sie nicht erreichen kann.
|
||||
- **Prisma wollte sieben handgeschriebene Objekte löschen.** Die Migration musste von Hand
|
||||
geschrieben werden; zusätzlich fehlten `@default(now())` bei den `@updatedAt`-Feldern, weshalb
|
||||
Prisma bei jedem Lauf `DROP DEFAULT` vorschlug.
|
||||
- **Zwei gleichzeitige Testläufe gegen `laufzettel_test`** ergaben 62 falsche Fehlschläge. Der
|
||||
Review-Agent und ich haben uns die Daten weggetruncated; im Auftrag an den Agenten hatte ich die
|
||||
eigene Datenbank nicht verlangt.
|
||||
|
||||
### Nächster Schritt
|
||||
|
||||
- #010 Vorlagen-Editor: Liste mit Suche und Filtern, Abschnitte und Schritte per Drag & Drop,
|
||||
Vorschau, Veröffentlichen, Versionshistorie, Warnung bei unbekannten Platzhaltern.
|
||||
|
||||
@@ -15,7 +15,7 @@ Nicht im MVP umsetzen. Der Weg dorthin wird im MVP aber bewusst freigehalten: Di
|
||||
- [ ] Connectors: LDAP/AD schreibend über eigenes Dienstkonto, Microsoft Graph (Entra ID, Exchange Online, Intune), authentik-API, später Ticketsystem und RMM
|
||||
- [ ] Onboarding als Aktion: Konto anlegen, Initialpasswort erzeugen und sicher übergeben, Gruppen laut Abteilung, Postfach und Lizenz
|
||||
- [ ] Offboarding als Aktion: Konto sperren, Sitzungen widerrufen, Postfach in Shared Mailbox wandeln
|
||||
- [ ] Vorschau „das wird passieren", ausdrückliche Bestätigung, Ergebnis-Log am Schritt, Rollback-Hinweis
|
||||
- [ ] Vorschau „das wird passieren“, ausdrückliche Bestätigung, Ergebnis-Log am Schritt, Rollback-Hinweis
|
||||
- [ ] Fehler setzen den Schritt auf `FEHLGESCHLAGEN` mit Meldung
|
||||
- [ ] Berechtigung je Connector und Rolle, Geheimnisse verschlüsselt, alle Aufrufe im `Protokoll`
|
||||
- [ ] Wiederkehrende Läufe (v0.5.0)
|
||||
|
||||
@@ -31,7 +31,7 @@ Ein lauffähiges, leeres Projekt mit allen Qualitätsschranken, dem gehärteten
|
||||
- [x] Design-Token als CSS-Variablen (Grund `#0B0D10`, Flächen `#14171C`/`#1B1F26`, Linien `#262B33`, Text `#F3F5F7`/`#98A2B3`, Akzent `#C6F135`, Status `#3DDC97`/`#F5B84A`/`#F87171`), heller Modus optional
|
||||
- [x] Schriften selbst gehostet: `@fontsource-variable/space-grotesk`, `-manrope`, `-jetbrains-mono`
|
||||
- [x] Wortmarke `laufzettel` (Space Grotesk 700, Laufweite −0.04em, Akzentpunkt) und Bildmarke `[ ✓ ]` als SVG, Favicon, PWA-Icon, `manifest.ts`
|
||||
- [x] App-Shell: dunkle Sidebar mit „Scannen"-Knopf direkt unter dem Logo, Pillen-Buttons (40 px, am Handy 44–46 px), Karten mit Radius 16–18 px und 1-px-Linie ohne Schatten
|
||||
- [x] App-Shell: dunkle Sidebar mit „Scannen“-Knopf direkt unter dem Logo, Pillen-Buttons (40 px, am Handy 44–46 px), Karten mit Radius 16–18 px und 1-px-Linie ohne Schatten
|
||||
- [x] Versionsanzeige unten links aus `NEXT_PUBLIC_APP_VERSION` und `NEXT_PUBLIC_GIT_SHA`
|
||||
- [x] `README.md`, `CHANGELOG.md` (Keep a Changelog, deutsch), `docs/devlog.md`, `CLAUDE.md` mit Konventionen und Namensabbildung, `docs/architektur.md`, `docs/abnahme-mvp.md`
|
||||
- [x] Gitea: Labels, Milestones `v0.1.0`…`v0.5.0`, Epic-Issues #001–#004 und Meilenstein-Issues #005–#019
|
||||
@@ -65,4 +65,4 @@ Ein lauffähiges, leeres Projekt mit allen Qualitätsschranken, dem gehärteten
|
||||
Paket `radix-ui`.
|
||||
- **Port 3000 war von ProxLedger belegt**, die Abnahme lief deshalb auf 3100 bzw. 3200.
|
||||
- Die Migrationen laufen im Compose-Stand nachweislich automatisch; eingespielt wird ab #006
|
||||
etwas, vorher meldet der Dienst korrekt „No pending migrations to apply".
|
||||
etwas, vorher meldet der Dienst korrekt „No pending migrations to apply“.
|
||||
|
||||
@@ -51,7 +51,7 @@ Anmeldung per generischem OIDC (Ziel authentik) und als Notfallzugang lokal per
|
||||
gewesen. Jetzt ein echter argon2-Hash, einmal lazy erzeugt. Nachgemessen: rund 1 ms Unterschied
|
||||
über je 60 Anfragen, über ein Netz nicht auswertbar.
|
||||
- **`overflow-x: hidden` auf `body` hat meinen eigenen Mobil-Test blind gemacht.** Auf der
|
||||
Benutzerseite war bei 390 px der Knopf „Setzen" abgeschnitten, die Prüfung
|
||||
Benutzerseite war bei 390 px der Knopf „Setzen“ abgeschnitten, die Prüfung
|
||||
`scrollWidth > clientWidth` konnte das aber nicht sehen. Das `hidden` ist raus, die Liste sind
|
||||
Karten statt einer Tabelle.
|
||||
- **Jede OIDC-Anmeldung hätte die Rolle überschrieben**, auch ohne konfigurierte
|
||||
|
||||
@@ -17,7 +17,7 @@ Ein Codepfad für beide Betriebsmodi. Im Modus `INTERN` ist die Kunden-Ebene vol
|
||||
- [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] 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/`
|
||||
@@ -38,8 +38,8 @@ Ein Codepfad für beide Betriebsmodi. Im Modus `INTERN` ist die Kunden-Ebene vol
|
||||
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
|
||||
„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
|
||||
@@ -49,11 +49,11 @@ Ein Codepfad für beide Betriebsmodi. Im Modus `INTERN` ist die Kunden-Ebene vol
|
||||
- **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
|
||||
- **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.
|
||||
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.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# #009 M4 – Vorlagen-Versionierung im Service
|
||||
|
||||
**Status:** offen
|
||||
**Status:** erledigt
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend
|
||||
**Geschätzt:** 6 h **Tatsächlich:** –
|
||||
**Geschätzt:** 6 h **Tatsächlich:** 1 h 20 min
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -11,22 +11,63 @@ Die harte Regel der Anwendung: eine veröffentlichte Version ist für immer unve
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Services für `Vorlage`, `VorlagenVersion`, `VorlagenAbschnitt`, `VorlagenSchritt`, `VorlagenVariable`
|
||||
- [ ] `vorlageVeroeffentlichenService`: setzt `status = VEROEFFENTLICHT`, `veroeffentlichtAm`, `veroeffentlichtVonId`, Änderungsnotiz, `Protokoll`-Eintrag
|
||||
- [ ] `vorlageBearbeitenService`: tiefe Kopie der letzten veröffentlichten Version als neuer Entwurf mit fortlaufender `version`; kopiert Variablen, Abschnitte und Schritte samt `konfig`
|
||||
- [ ] Höchstens ein Entwurf je Vorlage — zweiter Versuch wirft `VorlagenFehler` mit Verweis auf den bestehenden Entwurf
|
||||
- [ ] Jede Schreiboperation auf einer veröffentlichten Version wirft; zentral in `versionMussEntwurfSein()`
|
||||
- [ ] Läufe verweisen ausschließlich auf veröffentlichte Versionen — beim Anlegen geprüft
|
||||
- [ ] `vorlageDuplizierenService`: neue Vorlage mit Entwurf als Kopie
|
||||
- [ ] JSON-Export und -Import mit Zod-Schema, Format in `docs/vorlagen-format.md` dokumentiert (Version des Formats, Abschnitte, Schritte, Variablen, `konfig`)
|
||||
- [ ] Rechte: `REDAKTEUR` und `ADMIN` dürfen anlegen, bearbeiten, veröffentlichen; `TECHNIKER` und `LESER` nicht
|
||||
- [x] Services für `Vorlage`, `VorlagenVersion`, `VorlagenAbschnitt`, `VorlagenSchritt`, `VorlagenVariable`
|
||||
- [x] `versionVeroeffentlichen`: setzt `status = VEROEFFENTLICHT`, `veroeffentlichtAm`, `veroeffentlichtVonId`, Änderungsnotiz, `Protokoll`-Eintrag
|
||||
- [x] `entwurfHolenOderErzeugen`: tiefe Kopie der letzten veröffentlichten Version als neuer Entwurf mit fortlaufender `version`; kopiert Variablen, Abschnitte und Schritte samt `konfig`
|
||||
- [x] Höchstens ein Entwurf je Vorlage — durchgesetzt vom partiellen Eindeutigkeitsindex `VorlagenVersion_ein_entwurf`, der zweite gleichzeitige Aufruf bekommt denselben Entwurf
|
||||
- [x] Jede Schreiboperation auf einer veröffentlichten Version wirft; zentral in `versionMussEntwurfSein()` — **und** zusätzlich in der Datenbank per Trigger
|
||||
- [x] Läufe verweisen ausschließlich auf veröffentlichte Versionen — `veroeffentlichteVersionPruefen()` für #013
|
||||
- [x] `vorlageDuplizieren`: neue Vorlage mit Entwurf als Kopie
|
||||
- [x] JSON-Export und -Import mit Zod-Schema, Format in `docs/vorlagen-format.md` dokumentiert (Version des Formats, Abschnitte, Schritte, Variablen, `konfig`)
|
||||
- [x] Rechte: `REDAKTEUR` und `ADMIN` dürfen anlegen, bearbeiten, veröffentlichen; `TECHNIKER` und `LESER` nicht
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: nach dem Veröffentlichen schlägt jede Änderung an Version, Abschnitten, Schritten und Variablen fehl
|
||||
- [ ] Test: „Bearbeiten" erzeugt eine tiefe Kopie — Änderungen am Entwurf lassen die veröffentlichte Version unberührt, auch in verschachtelten `konfig`-Feldern
|
||||
- [ ] Test: ein zweiter Entwurf wird abgelehnt
|
||||
- [ ] Test: ein Lauf auf Version 1 bleibt nach Veröffentlichen von Version 2 unverändert und zeigt weiter die Schritte aus Version 1
|
||||
- [ ] Test: Export und anschließender Import ergeben eine inhaltsgleiche Vorlage
|
||||
- [ ] Test: `TECHNIKER` darf nicht veröffentlichen
|
||||
- [ ] Test: Veröffentlichen schreibt einen `Protokoll`-Eintrag
|
||||
- [x] Test: nach dem Veröffentlichen schlägt jede Änderung an Version, Abschnitten, Schritten und Variablen fehl
|
||||
- [x] Test: „Bearbeiten“ erzeugt eine tiefe Kopie — Änderungen am Entwurf lassen die veröffentlichte Version unberührt, auch in verschachtelten `konfig`-Feldern
|
||||
- [x] Test: ein zweiter Entwurf wird abgelehnt
|
||||
- [x] Test: ein Lauf auf Version 1 bleibt nach Veröffentlichen von Version 2 unverändert und zeigt weiter die Schritte aus Version 1
|
||||
- [x] Test: Export und anschließender Import ergeben eine inhaltsgleiche Vorlage
|
||||
- [x] Test: `TECHNIKER` darf nicht veröffentlichen
|
||||
- [x] Test: Veröffentlichen schreibt einen `Protokoll`-Eintrag
|
||||
|
||||
## Beim Bauen aufgefallen
|
||||
|
||||
- **Die Lesemodelle haben die Mandantengrenze verloren, sobald jemand suchte.** Der Suchfilter
|
||||
brachte einen zweiten `OR`-Schlüssel in dasselbe Objekt; in JavaScript gewinnt der letzte, also
|
||||
ersetzte er die Kundenbedingung. Ein einzelner Buchstabe im Suchfeld gab einem gebundenen
|
||||
Techniker alle Vorlagen aller Kunden. `versionsHistorie` und `versionMitAufbau` haben überhaupt
|
||||
nicht gefiltert — damit lagen Abschnittstitel, Schritte und PowerShell-Befehle fremder Kunden
|
||||
offen. Ursache ist banal: zu `src/server/queries/vorlagen.ts` gab es keinen einzigen Test. Es
|
||||
gibt jetzt `tests/vorlagen-sicht.test.ts`, und die Kundenbedingung steht in einem `AND`-Eintrag,
|
||||
wo kein weiterer Filter sie überschreiben kann.
|
||||
- **Die Unveränderlichkeit hing allein am Service.** Migrationen, `psql`, ein Seed-Skript und ab
|
||||
Phase 4 die Connectoren schreiben daran vorbei — und genau diese Regel ist der Grund, warum ein
|
||||
Archiv-Lauf in zehn Jahren noch etwas wert ist. Vier Trigger setzen sie jetzt in der Datenbank
|
||||
durch, einer davon verbietet den Rückweg `VEROEFFENTLICHT → ENTWURF`, der sonst alles andere
|
||||
ausgehebelt hätte.
|
||||
- **Prisma will handgeschriebene Datenbankobjekte löschen.** `prisma migrate dev` erzeugte für die
|
||||
neue Migration sieben `DROP`-Anweisungen gegen Trigger, zusammengesetzte Fremdschlüssel und
|
||||
Trigramm-Indizes — aus Prismas Sicht sind sie Abweichungen von der Vorgabe. Wer sie einmal
|
||||
übersieht, entfernt stillschweigend die Mandantengrenze. `tests/datenbankregeln.test.ts` zählt
|
||||
diese Objekte jetzt namentlich auf und macht jeden Verlust laut.
|
||||
- **Die Trigger haben schlechte Test-Fixtures entlarvt.** Mehrere legten eine Version als
|
||||
`VEROEFFENTLICHT` an und füllten sie *danach* — also genau das, was die Regel verbietet. Der
|
||||
Service hatte das nie gesehen, weil die Fixtures direkt über Prisma schrieben.
|
||||
- **Zwei Redakteure am selben Entwurf überschrieben sich spurlos.** Entwürfe werden bewusst nicht
|
||||
protokolliert, die Arbeit wäre also auch nicht wiederherstellbar gewesen. `entwurfSpeichern`
|
||||
nimmt jetzt den erwarteten Stand (`geaendertAm`) und weist ein Speichern auf einem überholten
|
||||
Stand ab.
|
||||
- **`Zod` war achtmal einzeln eingebunden**, die deutschen Meldungen hingen davon ab, dass jede
|
||||
Datei daran denkt. Jetzt gibt es `src/lib/validierung.ts` als einzige Quelle, und eine
|
||||
ESLint-Regel weist einen direkten `zod`-Import ab.
|
||||
- **Der Doku-Link eines Schritts nahm jedes Schema an**, auch `javascript:alert(1)`. Er landet in
|
||||
der Abarbeitungsansicht als `href` — über eine importierte Datei wäre das fremder Code im
|
||||
Browser des Technikers. Erlaubt sind jetzt nur `http` und `https`.
|
||||
- **Der Export verwarf bei jeder Beanstandung das ganze `konfig`.** Ein `shell: "zsh"` aus einer
|
||||
späteren Fassung kostete lautlos auch den `befehl` — in genau der Datei, die jemand zum Umzug
|
||||
braucht. Exportiert wird jetzt der rohe Inhalt; abgewiesen wird beim Import, laut und mit Pfad.
|
||||
- **Zwei gleichzeitige Testläufe gegen dieselbe Datenbank** (mein Lauf und der des Review-Agenten)
|
||||
ergaben 62 falsche Fehlschläge, weil `TRUNCATE` dem anderen die Daten unter den Füßen wegzieht.
|
||||
Das war mein Fehler im Auftrag an den Agenten; `CLAUDE.md` nennt jetzt den Befehl für eine
|
||||
eigene Review-Datenbank.
|
||||
|
||||
@@ -11,7 +11,7 @@ Der Editor, in dem ein Redakteur eine Vorlage baut, ohne den Kopf über Versione
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Vorlagenliste mit Suche, Filter nach Kategorie und Tag, Kennzeichnung „Entwurf vorhanden"
|
||||
- [ ] Vorlagenliste mit Suche, Filter nach Kategorie und Tag, Kennzeichnung „Entwurf vorhanden“
|
||||
- [ ] Editor: Abschnitte und Schritte anlegen, bearbeiten, löschen; Abschnitte mit zweistelliger Nummer (`01 · Vorbereitung`)
|
||||
- [ ] Sortieren per Drag & Drop mit `@dnd-kit`, auch am Handy mit dem Finger bedienbar
|
||||
- [ ] Je Schritt: Titel, Typ (`HAKEN`, `TEXT`, `ZAHL`, `AUSWAHL`, `DATUM`, `BEFEHL`), Pflicht (roter Stern), Hilfetext als Markdown, Doku-Link
|
||||
@@ -25,7 +25,7 @@ Der Editor, in dem ein Redakteur eine Vorlage baut, ohne den Kopf über Versione
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Eine Vorlage lässt sich vollständig ohne Umweg über die Datenbank anlegen, sortieren, veröffentlichen
|
||||
- [ ] Eine veröffentlichte Version ist im Editor nur lesend; „Bearbeiten" erzeugt sichtbar einen Entwurf
|
||||
- [ ] Eine veröffentlichte Version ist im Editor nur lesend; „Bearbeiten“ erzeugt sichtbar einen Entwurf
|
||||
- [ ] Unbekannte Platzhalter werden als Warnung angezeigt, blockieren aber das Speichern nicht
|
||||
- [ ] Sortieren funktioniert am Handy bei 390 px Breite ohne horizontales Scrollen
|
||||
- [ ] Der Markdown-Hilfetext rendert ohne rohes HTML
|
||||
|
||||
@@ -12,12 +12,12 @@ 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
|
||||
- [ ] 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ä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
|
||||
|
||||
@@ -17,16 +17,16 @@ Reine, framework-freie Service-Schicht mit dichter Testabdeckung. Hier entstehen
|
||||
- [ ] Aus der gewählten Abteilung: `{{ou}}` falls nicht vom Mitarbeiter, `{{gruppen}}` kommagetrennt
|
||||
- [ ] `{{ou}}` eines synchronisierten Mitarbeiters ist der Eltern-Container seines `dn` (alles nach dem ersten Komma)
|
||||
- [ ] Dazu die in der Vorlage definierten Variablen; unbekannte Platzhalter bleiben sichtbar stehen
|
||||
- [ ] `{{generiertes_passwort}}`: 20 Zeichen aus Groß, Klein, Ziffern und Sonderzeichen ohne verwechselbare Zeichen, je Anzeige neu erzeugt, **nie** gespeichert; Hinweis „Passwort sofort in den Passwortmanager übernehmen"; in der Druckansicht `[Passwort – nicht gespeichert]`
|
||||
- [ ] `{{generiertes_passwort}}`: 20 Zeichen aus Groß, Klein, Ziffern und Sonderzeichen ohne verwechselbare Zeichen, je Anzeige neu erzeugt, **nie** gespeichert; Hinweis „Passwort sofort in den Passwortmanager übernehmen“; in der Druckansicht `[Passwort – nicht gespeichert]`
|
||||
- [ ] Namensregeln (`Einstellung.namensregeln` global, im Modus `DIENSTLEISTUNG` zusätzlich `Kunde.namensregeln` mit Fallback): `upn`, `sam` (höchstens 20 Zeichen), `mail`, `hostname`
|
||||
- [ ] Transliteration (ä→ae, ö→oe, ü→ue, ß→ss, Akzente entfernt), Kleinschreibung, Leerzeichen entfernt, `{nnn}`-Zähler, `{v}` für den ersten Buchstaben des Vornamens, `{kundenkuerzel}`
|
||||
- [ ] Shell-Escaping je Shell: PowerShell (doppelte Anführungszeichen verdoppeln, Backtick maskieren), Bash, Cmd
|
||||
- [ ] HTML-Escaping ersetzter Werte; Markdown im Hilfetext sicher gerendert, kein rohes HTML
|
||||
- [ ] „Alle Befehle als Skript kopieren" je Abschnitt: alle `BEFEHL`-Schritte in Reihenfolge, getrennt durch Leerzeilen, je mit `# Schritt: <Titel>` davor, Zeilenumbruch passend zur Shell
|
||||
- [ ] „Alle Befehle als Skript kopieren“ je Abschnitt: alle `BEFEHL`-Schritte in Reihenfolge, getrennt durch Leerzeilen, je mit `# Schritt: <Titel>` davor, Zeilenumbruch passend zur Shell
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: „Jörg Müller-Lüdenscheid" ergibt `joerg.mueller-luedenscheid@firma.de` und `jmuellerluedensch` (auf 20 Zeichen gekürzt)
|
||||
- [ ] Test: „Jörg Müller-Lüdenscheid“ ergibt `joerg.mueller-luedenscheid@firma.de` und `jmuellerluedensch` (auf 20 Zeichen gekürzt)
|
||||
- [ ] Test: `{{generiertes_passwort}}` erscheint im angezeigten Befehl, steht aber nicht in `Schrittergebnis`, `Kommentar`, `Protokoll` oder der Druckansicht; zwei Aufrufe liefern verschiedene Passwörter
|
||||
- [ ] Test: ein Wert mit `"` und `` ` `` wird für PowerShell korrekt maskiert und bricht den Befehl nicht
|
||||
- [ ] Test: unbekannte Platzhalter bleiben als `{{key}}` stehen
|
||||
|
||||
@@ -22,14 +22,14 @@ Das Herz der Anwendung: ein Lauf wird vorbereitet, abgearbeitet, abgeschlossen u
|
||||
- [ ] `laufArchivierenService` und `laufZurueckholenService` (nur `ADMIN`), beide mit `Protokoll`-Eintrag
|
||||
- [ ] `laufMussVeraenderbarSein(lauf)` zentral, aufgerufen vor jeder Änderung an Kopfdaten, `Schrittergebnis` und `Kommentar`
|
||||
- [ ] Kommentare je Lauf und je Schrittergebnis
|
||||
- [ ] „Mit AD-Konto verknüpfen": ein Lauf mit `mitarbeiterName` wird nachträglich über UPN oder Name mit einem `Mitarbeiter` verbunden
|
||||
- [ ] „Mit AD-Konto verknüpfen“: ein Lauf mit `mitarbeiterName` wird nachträglich über UPN oder Name mit einem `Mitarbeiter` verbunden
|
||||
- [ ] Start-Dialog: Vorlage → Mitarbeiter → Gerät (wählen, neu anlegen oder scannen) → Ticket, Variablen, Zuweisung, Fälligkeit; Vorbelegung je Aufrufort, Besitzer des Geräts als Mitarbeiter vorgeschlagen; Vorschläge aus den Namensregeln für `upn`, `sam`, `mail`
|
||||
- [ ] Abarbeitungsansicht: einklappbare Abschnitte mit `01 ·`-Nummerierung, Fortschrittsbalken in Lime, je Schritt Zustand, Wert, Notiz, Kommentare und „wer wann"
|
||||
- [ ] Abarbeitungsansicht: einklappbare Abschnitte mit `01 ·`-Nummerierung, Fortschrittsbalken in Lime, je Schritt Zustand, Wert, Notiz, Kommentare und „wer wann“
|
||||
- [ ] Erledigte Schritte durchgestrichen in Nebentext-Farbe, Checkbox erledigt als Lime-Kachel mit dunklem Haken, Pflichtschritte mit rotem Stern
|
||||
- [ ] `BEFEHL`-Schritte als Codeblock auf `#0B0D10` mit Lime-Schrift, Kopier-Knopf, Warnung wenn ein Platzhalter unaufgelöst ist; je Abschnitt „Alle Befehle als Skript kopieren"
|
||||
- [ ] `BEFEHL`-Schritte als Codeblock auf `#0B0D10` mit Lime-Schrift, Kopier-Knopf, Warnung wenn ein Platzhalter unaufgelöst ist; je Abschnitt „Alle Befehle als Skript kopieren“
|
||||
- [ ] `ZAHL` außerhalb von Min/Max wird markiert, aber nicht blockiert
|
||||
- [ ] Route Handler `GET /api/laeufe/[id]/zustand`, Client pollt alle 10 s und pausiert bei verstecktem Tab
|
||||
- [ ] Umschalter „Kompakt / Ausführlich", ausführlich klappt alle Hilfetexte auf
|
||||
- [ ] Umschalter „Kompakt / Ausführlich“, ausführlich klappt alle Hilfetexte auf
|
||||
- [ ] Abschließen und Archivieren als getrennte Aktionen
|
||||
- [ ] Screenshots Start-Dialog, Lauf Desktop, Lauf Handy, Befehlsschritt in `docs/screenshots/m8/`
|
||||
|
||||
|
||||
@@ -12,24 +12,24 @@ Der Einstieg für den Techniker, der am Gerät steht: scannen, sehen was zu tun
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Stapel-Erzeugung von N unregistrierten Tags (Standard 50) für vorgedruckte Aufkleber
|
||||
- [ ] Route `/t/[tagCode]` mit Anmeldepflicht und Rücksprung; drei Fälle: registriert → Geräteseite; existiert unregistriert → Registrierformular; unbekannt → Hinweis „Kein Laufzettel-Tag" mit Link zur Suche
|
||||
- [ ] Route `/t/[tagCode]` mit Anmeldepflicht und Rücksprung; drei Fälle: registriert → Geräteseite; existiert unregistriert → Registrierformular; unbekannt → Hinweis „Kein Laufzettel-Tag“ mit Link zur Suche
|
||||
- [ ] Registrierformular: Typ, Hostname oder Bezeichnung, Seriennummer, Inventarnummer, optional Besitzer per Mitarbeitersuche; danach Geräteseite
|
||||
- [ ] Geräteseite `/geraete/[id]`, handyoptimiert: Kopf mit Hostname bzw. Bezeichnung, Typ, Besitzer mit Abteilung, Seriennummer, `tagCode` in JetBrains Mono
|
||||
- [ ] Abschnitt „Zugewiesen": alle Läufe zu diesem Gerät in `OFFEN` und `IN_ARBEIT` mit Fortschritt, Zuweisung und Fälligkeit, Knopf „Starten" bzw. „Weiter"
|
||||
- [ ] Abschnitt „Schnellstart": Vorlagen aus `GeraetetypVorlage` für diesen Gerätetyp, ein Tipp startet den Lauf mit vorbelegtem Gerät und Besitzer
|
||||
- [ ] Abschnitt „Vorlage zuweisen": Vorlage wählen, optional Techniker und Fälligkeit, erzeugt einen Lauf in `OFFEN`
|
||||
- [ ] Abschnitt „Historie": abgeschlossene und archivierte Läufe chronologisch
|
||||
- [ ] Abschnitt „Zugewiesen“: alle Läufe zu diesem Gerät in `OFFEN` und `IN_ARBEIT` mit Fortschritt, Zuweisung und Fälligkeit, Knopf „Starten“ bzw. „Weiter“
|
||||
- [ ] Abschnitt „Schnellstart“: Vorlagen aus `GeraetetypVorlage` für diesen Gerätetyp, ein Tipp startet den Lauf mit vorbelegtem Gerät und Besitzer
|
||||
- [ ] Abschnitt „Vorlage zuweisen“: Vorlage wählen, optional Techniker und Fälligkeit, erzeugt einen Lauf in `OFFEN`
|
||||
- [ ] Abschnitt „Historie“: abgeschlossene und archivierte Läufe chronologisch
|
||||
- [ ] QR-Erzeugung serverseitig als SVG mit `qrcode`, Fehlerkorrektur M, Inhalt ausschließlich `{APP_URL}/t/{tagCode}`, keine Personendaten, keine externe API
|
||||
- [ ] Etikettendruck mit Print-CSS in zwei Layouts: Etikettendrucker 62 × 29 mm (ein Etikett pro Seite) und A4-Bogen 3 × 8 à 70 × 37 mm; je Etikett QR, `tagCode` lesbar darunter, optional Hostname und Besitzer, kleines Laufzettel-Logo; immer hell
|
||||
- [ ] Im Modus `DIENSTLEISTUNG` zeigt das Etikett zusätzlich das Kundenkürzel, im Modus `INTERN` nie
|
||||
- [ ] Knopf „Etikett drucken" auf der Geräteseite, Stapeldruck für unregistrierte Tags
|
||||
- [ ] „Scannen" in der App: `barcode-detector` (native BarcodeDetector-API mit Polyfill-Fallback), Kamera im Browser, direkter Sprung zur Geräteseite
|
||||
- [ ] Knopf „Etikett drucken“ auf der Geräteseite, Stapeldruck für unregistrierte Tags
|
||||
- [ ] „Scannen“ in der App: `barcode-detector` (native BarcodeDetector-API mit Polyfill-Fallback), Kamera im Browser, direkter Sprung zur Geräteseite
|
||||
- [ ] Screenshots Geräteseite Handy, Registrierformular, Etikett 62 × 29, A4-Bogen, Scanner in `docs/screenshots/m9/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: `/t/LZ-7K3F9Q` ohne Anmeldung leitet zum Login und danach auf die Geräteseite von `PC-001`
|
||||
- [ ] Test: „Vorlage zuweisen" erzeugt einen Lauf in `OFFEN`, der unter „Zugewiesen" erscheint; „Starten" öffnet ihn und setzt `IN_ARBEIT`
|
||||
- [ ] Test: „Vorlage zuweisen“ erzeugt einen Lauf in `OFFEN`, der unter „Zugewiesen“ erscheint; „Starten“ öffnet ihn und setzt `IN_ARBEIT`
|
||||
- [ ] Test: unregistrierter Tag führt zum Registrierformular, danach zur Geräteseite; unbekannter Tag zeigt den Hinweis
|
||||
- [ ] Test: mehrere Zuweisungen je Gerät sind gleichzeitig möglich
|
||||
- [ ] Test: der QR-Inhalt ist genau `{APP_URL}/t/{tagCode}` und enthält keine Personendaten
|
||||
|
||||
@@ -16,11 +16,11 @@ Die Mitarbeiter der eigenen Firma kommen aus dem Active Directory, damit beim St
|
||||
- [ ] `explicitBufferAttributes: ['objectGUID']` und korrekte Umsortierung der gemischt-endianen Bytes zur GUID-Zeichenkette als `externeId`
|
||||
- [ ] Standardfilter `(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))`
|
||||
- [ ] Attribut-Mapping mit AD-Vorgaben: `givenName`, `sn`, `displayName`, `mail`, `userPrincipalName` → `upn`, `sAMAccountName` → `sam`, `department`, `title`, `manager` → `vorgesetzterExterneId`, `distinguishedName` → `dn`
|
||||
- [ ] „Verbindung testen": zeigt die ersten 10 Treffer mit gemappten Feldern, ohne etwas zu schreiben
|
||||
- [ ] „Verbindung testen“: zeigt die ersten 10 Treffer mit gemappten Feldern, ohne etwas zu schreiben
|
||||
- [ ] `abgleichService`: anlegen, aktualisieren, verschwundene oder deaktivierte Konten auf `aktiv = false` — **nie löschen**; Ergebnis (angelegt/aktualisiert/deaktiviert/Fehler) in `letzterAbgleichStatus`, `letzterAbgleichFehler`, `letzterAbgleichAm` und im `Protokoll`
|
||||
- [ ] Zeitplan mit `node-cron` im selben Prozess, Intervall je Quelle (Standard 60 min), Sperre über `AbgleichSperre` in der Datenbank, damit nur eine Instanz läuft; zusätzlich „Jetzt abgleichen"
|
||||
- [ ] Zeitplan mit `node-cron` im selben Prozess, Intervall je Quelle (Standard 60 min), Sperre über `AbgleichSperre` in der Datenbank, damit nur eine Instanz läuft; zusätzlich „Jetzt abgleichen“
|
||||
- [ ] Bind-Passwort verschlüsselt über `src/lib/geheimnisse.ts`, Serializer entfernt `bindPasswortVerschluesselt` aus jeder Antwort
|
||||
- [ ] Admin → Verzeichnis: Quelle anlegen und bearbeiten, Verbindung testen, Abgleich-Status und -Log, „Jetzt abgleichen"
|
||||
- [ ] Admin → Verzeichnis: Quelle anlegen und bearbeiten, Verbindung testen, Abgleich-Status und -Log, „Jetzt abgleichen“
|
||||
- [ ] Manuelle Mitarbeiter und CSV-Import (`givenName`, `sn`, `displayName`, `mail`, `upn`, `sam`, `department`, `title`)
|
||||
- [ ] Abteilungsvorschläge aus den `department`-Werten des Abgleichs, vom Admin in `Abteilung`-Einträge übernehmbar
|
||||
- [ ] Nur die gemappten Attribute speichern: keine Passwörter, keine Gruppenmitgliedschaften des Mitarbeiters
|
||||
|
||||
@@ -11,27 +11,27 @@ Die App ist nach dem Seed fachlich sofort brauchbar: sieben vollständige, fachl
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Vorlage 1 „PC Neuinstallation" (Client), Variablen `os_version`, `join_typ`; Abschnitte Vorbereitung, Installation, Einbindung, Software, Sicherheit, Benutzer, Abschluss
|
||||
- [ ] Vorlage 2 „Mitarbeiter-Onboarding" (Benutzer), Variablen `konto_typ`, `vorname`, `nachname`, `upn`, `sam`, `mail`, `abteilung`, `ou`, `gruppen`, `eintrittsdatum`, `vorgesetzter`; **beide** Konto-Abschnitte (AD und Entra ID), weil bedingte Abschnitte erst Phase 3 sind
|
||||
- [ ] Vorlage 3 „Mitarbeiter-Offboarding" (Benutzer), Variablen `austrittsdatum`, `ou_gesperrt`, `postfach_verbleib`, `vertreter`
|
||||
- [ ] Vorlage 4 „Server-Inbetriebnahme Windows-VM" (Server), Variablen `vmid`, `ip_adresse`, `vlan`, `rolle`, `os_version`
|
||||
- [ ] Vorlage 5 „Firewall-Rollout OPNsense" (Netzwerk), Variablen `wan_typ`, `lan_netz`, `vpn_typ`
|
||||
- [ ] Vorlage 6 „Monatlicher Patchday" (Wartung), Variable `monat`
|
||||
- [ ] Vorlage 7 „Backup-Restore-Test" (Wartung), Variablen `system`, `restore_typ`
|
||||
- [ ] Vorlage 1 „PC Neuinstallation“ (Client), Variablen `os_version`, `join_typ`; Abschnitte Vorbereitung, Installation, Einbindung, Software, Sicherheit, Benutzer, Abschluss
|
||||
- [ ] Vorlage 2 „Mitarbeiter-Onboarding“ (Benutzer), Variablen `konto_typ`, `vorname`, `nachname`, `upn`, `sam`, `mail`, `abteilung`, `ou`, `gruppen`, `eintrittsdatum`, `vorgesetzter`; **beide** Konto-Abschnitte (AD und Entra ID), weil bedingte Abschnitte erst Phase 3 sind
|
||||
- [ ] Vorlage 3 „Mitarbeiter-Offboarding“ (Benutzer), Variablen `austrittsdatum`, `ou_gesperrt`, `postfach_verbleib`, `vertreter`
|
||||
- [ ] Vorlage 4 „Server-Inbetriebnahme Windows-VM“ (Server), Variablen `vmid`, `ip_adresse`, `vlan`, `rolle`, `os_version`
|
||||
- [ ] Vorlage 5 „Firewall-Rollout OPNsense“ (Netzwerk), Variablen `wan_typ`, `lan_netz`, `vpn_typ`
|
||||
- [ ] Vorlage 6 „Monatlicher Patchday“ (Wartung), Variable `monat`
|
||||
- [ ] Vorlage 7 „Backup-Restore-Test“ (Wartung), Variablen `system`, `restore_typ`
|
||||
- [ ] Alle Schritte wortgetreu nach Auftrag mit Typ, Pflichtkennzeichen und `konfig`; die im Auftrag genannten Hilfetexte übernehmen, die übrigen fachlich korrekt und kurz ergänzen
|
||||
- [ ] 5 Mitarbeiter, darunter „Maria Huber", `m.huber@firma.example`, `sam` `mhuber`, Abteilung Buchhaltung, Vorgesetzte „Petra Klein"
|
||||
- [ ] 5 Mitarbeiter, darunter „Maria Huber“, `m.huber@firma.example`, `sam` `mhuber`, Abteilung Buchhaltung, Vorgesetzte „Petra Klein“
|
||||
- [ ] 3 Abteilungen: Buchhaltung → `OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local` mit Gruppen `G-Buchhaltung, G-Alle`; Vertrieb; IT
|
||||
- [ ] Eine **deaktivierte** `Verzeichnisquelle` als Beispielkonfiguration
|
||||
- [ ] Geräte `PC-001` (PC, `LZ-7K3F9Q`, Besitzerin Maria Huber) und `NB-001` (Notebook, `LZ-M2XH4T`), dazu 5 unregistrierte Tags
|
||||
- [ ] `GeraetetypVorlage`: PC und NOTEBOOK → „PC Neuinstallation"; SERVER → „Server-Inbetriebnahme Windows-VM" und „Backup-Restore-Test"; FIREWALL → „Firewall-Rollout OPNsense"
|
||||
- [ ] `SEED_MODE=DIENSTLEISTUNG` (Alias `SERVICE`) erzeugt stattdessen „Musterfirma GmbH" (`MUS`) und „Bäckerei Reiter" (`REI`) mit je einer Person, einer Abteilung und einem Gerät
|
||||
- [ ] `GeraetetypVorlage`: PC und NOTEBOOK → „PC Neuinstallation“; SERVER → „Server-Inbetriebnahme Windows-VM“ und „Backup-Restore-Test“; FIREWALL → „Firewall-Rollout OPNsense“
|
||||
- [ ] `SEED_MODE=DIENSTLEISTUNG` (Alias `SERVICE`) erzeugt stattdessen „Musterfirma GmbH“ (`MUS`) und „Bäckerei Reiter“ (`REI`) mit je einer Person, einer Abteilung und einem Gerät
|
||||
- [ ] Alles idempotent über `upsert` mit stabilen Schlüsseln
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] `pnpm db:seed` zweimal hintereinander: keine Fehler, keine Duplikate, keine geänderten Zeilen
|
||||
- [ ] Alle sieben Vorlagen liegen als `VEROEFFENTLICHT`, Version 1 vor
|
||||
- [ ] Im Onboarding-Lauf steht im Schritt „Konto anlegen" der vollständige `New-ADUser`-Befehl mit Name, UPN, `-Path "OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local"` und der Gruppen-Befehl mit `G-Buchhaltung, G-Alle`
|
||||
- [ ] Im Onboarding-Lauf steht im Schritt „Konto anlegen“ der vollständige `New-ADUser`-Befehl mit Name, UPN, `-Path "OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local"` und der Gruppen-Befehl mit `G-Buchhaltung, G-Alle`
|
||||
- [ ] `SEED_MODE=SERVICE` erzeugt `MUS-PC-001` gemäß Hostname-Regel `{kundenkuerzel}-PC-{nnn}`
|
||||
- [ ] Die Beispiel-Verzeichnisquelle ist deaktiviert und löst keinen Abgleich aus
|
||||
- [ ] Kein Seed-Datensatz enthält ein Klartext-Passwort außer dem Admin-Passwort aus der Umgebung, und das nur als argon2-Hash
|
||||
|
||||
@@ -11,10 +11,10 @@ Nachweis, dass die Abläufe wirklich durchlaufen, und eine Anleitung, mit der ei
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Playwright-Hauptablauf: anmelden → „PC Neuinstallation" für `PC-001` starten → Schritte abhaken → Wert eintragen → einen Schritt „nicht zutreffend" mit Notiz → abschließen → archivieren → im Archiv finden → Schreibschutz greift
|
||||
- [ ] Playwright-Hauptablauf: anmelden → „PC Neuinstallation“ für `PC-001` starten → Schritte abhaken → Wert eintragen → einen Schritt „nicht zutreffend“ mit Notiz → abschließen → archivieren → im Archiv finden → Schreibschutz greift
|
||||
- [ ] Derselbe Hauptablauf mit `SEED_MODE=SERVICE` gegen `MUS-PC-001`, mit Kundenwahl im Start-Dialog
|
||||
- [ ] Playwright-QR-Ablauf: `/t/LZ-7K3F9Q` ohne Anmeldung → Login → Geräteseite → „Vorlage zuweisen" → Lauf erscheint unter „Zugewiesen" → „Starten" setzt `IN_ARBEIT`; dazu unregistrierter Tag → Registrierformular → Geräteseite; unbekannter Tag → Hinweis
|
||||
- [ ] Playwright-Onboarding-Ablauf: Vorlage starten → Vorname und Nachname eingeben → UPN, `sam` und Mail werden vorgeschlagen → Abteilung „Buchhaltung" wählen → im Schritt „Konto anlegen" steht der fertige `New-ADUser`-Befehl → Kopieren liefert exakt diesen Text (Clipboard auslesen)
|
||||
- [ ] Playwright-QR-Ablauf: `/t/LZ-7K3F9Q` ohne Anmeldung → Login → Geräteseite → „Vorlage zuweisen“ → Lauf erscheint unter „Zugewiesen“ → „Starten“ setzt `IN_ARBEIT`; dazu unregistrierter Tag → Registrierformular → Geräteseite; unbekannter Tag → Hinweis
|
||||
- [ ] Playwright-Onboarding-Ablauf: Vorlage starten → Vorname und Nachname eingeben → UPN, `sam` und Mail werden vorgeschlagen → Abteilung „Buchhaltung“ wählen → im Schritt „Konto anlegen“ steht der fertige `New-ADUser`-Befehl → Kopieren liefert exakt diesen Text (Clipboard auslesen)
|
||||
- [ ] Playwright-Mobilprüfung: auf allen Hauptseiten bei 390 × 844 ist `scrollWidth <= clientWidth`
|
||||
- [ ] Playwright-Rollenprüfung: `LESER` sieht keine Schreib-Knöpfe und wird serverseitig abgewiesen
|
||||
- [ ] `pnpm docs:screenshots`: Playwright-Skript, Desktop 1440 × 900 und Handy 390 × 844, Seed-Daten, dunkles Design, mit Markierungen (Rahmen, Nummern) für die beschriebenen Stellen; Ablage `docs/anleitung/bilder/`
|
||||
|
||||
@@ -13,7 +13,7 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac
|
||||
| #006 | [M1 – Datenmodell](006-datenmodell.md) | erledigt | v0.1.0 | 5 h | 1 h 10 min |
|
||||
| #007 | [M2 – Auth, Rollen, Benutzer](007-auth-rollen-benutzer.md) | erledigt | v0.1.0 | 8 h | 1 h 35 min |
|
||||
| #008 | [M3 – Betriebsmodus und Kunden](008-betriebsmodus-kunden.md) | erledigt | v0.1.0 | 6 h | 1 h 20 min |
|
||||
| #009 | [M4 – Vorlagen-Versionierung im Service](009-vorlagen-versionierung.md) | offen | v0.1.0 | 6 h | – |
|
||||
| #009 | [M4 – Vorlagen-Versionierung im Service](009-vorlagen-versionierung.md) | erledigt | v0.1.0 | 6 h | 1 h 20 min |
|
||||
| #010 | [M5 – Vorlagen-Editor](010-vorlagen-editor.md) | offen | v0.1.0 | 10 h | – |
|
||||
| #011 | [M6 – Mitarbeiter, Abteilungen, Geräte](011-mitarbeiter-abteilungen-geraete.md) | offen | v0.1.0 | 9 h | – |
|
||||
| #012 | [M7 – Platzhalter, Namensregeln, Escaping](012-platzhalter-namensregeln.md) | offen | v0.1.0 | 8 h | – |
|
||||
@@ -24,4 +24,4 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac
|
||||
| #017 | [M12 – Seed der sieben Vorlagen](017-seed-sieben-vorlagen.md) | offen | v0.1.0 | 8 h | – |
|
||||
| #018 | [M13 – E2E-Tests, Anleitung, PDF](018-e2e-anleitung-pdf.md) | offen | v0.1.0 | 14 h | – |
|
||||
| #019 | [M14 – Betriebswege und Release v0.1.0](019-betrieb-release.md) | offen | v0.1.0 | 10 h | – |
|
||||
| | **Summe** | | | **136 h** | **5 h 30 min** |
|
||||
| | **Summe** | | | **136 h** | **6 h 50 min** |
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
# Austauschformat für Vorlagen
|
||||
|
||||
Eine Vorlage lässt sich als JSON-Datei exportieren und wieder einspielen. Diese Datei beschreibt
|
||||
das Format — für jemanden, der eine Vorlage von Hand schreiben oder ein Skript dafür bauen will.
|
||||
|
||||
Die Prüfung liegt in [`src/lib/vorlagen-format.ts`](../src/lib/vorlagen-format.ts), Export und
|
||||
Import in [`src/server/services/vorlagen-austausch.ts`](../src/server/services/vorlagen-austausch.ts).
|
||||
Beide benutzen dasselbe Schema: was hier steht, gilt für den Export, den Import und den Editor.
|
||||
|
||||
## Wozu das Format da ist
|
||||
|
||||
Eine exportierte Vorlage soll sich in eine **andere** Laufzettel-Installation einspielen lassen.
|
||||
Deshalb enthält das Dokument nur das, was eine Vorlage inhaltlich ausmacht: Kopfdaten, Variablen,
|
||||
Abschnitte, Schritte. Alles, was nur in der Quellinstallation eine Bedeutung hat, bleibt draußen.
|
||||
|
||||
**Nicht im Dokument enthalten:**
|
||||
|
||||
- **Kennungen** (`id`, `versionId`, `abschnittId`) — sie zeigen in der Zielinstallation ins Leere
|
||||
- **Zeitstempel** (`erstelltAm`, `geaendertAm`, `veroeffentlichtAm`) und der veröffentlichende
|
||||
Benutzer
|
||||
- **Kundenbezug** (`kundeId`) — eine eingespielte Vorlage gilt zunächst für alle Kunden; wer sie
|
||||
einem Kunden zuordnen will, tut das danach in der Oberfläche
|
||||
- **`position`** — die Reihenfolge steckt in der Reihenfolge der Listen. Zwei Wahrheiten über
|
||||
dieselbe Reihenfolge laufen früher oder später auseinander.
|
||||
- **Läufe**, Schrittergebnisse, Kommentare und das Protokoll — das ist Betriebsgeschehen, keine
|
||||
Vorlage
|
||||
- der Status der Version und das Archiv-Kennzeichen
|
||||
|
||||
## Beispiel
|
||||
|
||||
Das folgende Dokument ist vollständig: zwei Abschnitte, zwei Variablen und ein `BEFEHL`-Schritt mit
|
||||
Platzhaltern. **JSON kennt keine Kommentare** — die Zeilen mit `//` müssen vor dem Import raus.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Fassung des Austauschformats, derzeit immer 1.
|
||||
"format": 1,
|
||||
"name": "Onboarding Notebook",
|
||||
"beschreibung": "Neue Person, neues Gerät.",
|
||||
"kategorie": "Onboarding",
|
||||
"schlagworte": ["notebook", "eintritt"],
|
||||
// Nur zur Information: welche Version der Quelle exportiert wurde. Beim
|
||||
// Import wird der Wert gelesen und nicht übernommen.
|
||||
"version": 3,
|
||||
"struktur": {
|
||||
// Wird beim Start eines Laufs abgefragt und füllt die Platzhalter.
|
||||
"variablen": [
|
||||
{ "schluessel": "vorname", "beschriftung": "Vorname", "typ": "TEXT", "pflicht": true },
|
||||
{
|
||||
"schluessel": "abteilung",
|
||||
"beschriftung": "Abteilung",
|
||||
"typ": "AUSWAHL",
|
||||
"optionen": ["IT", "Vertrieb", "Buchhaltung"]
|
||||
}
|
||||
],
|
||||
// Die Reihenfolge dieser Liste ist die Reihenfolge in der Ansicht.
|
||||
"abschnitte": [
|
||||
{
|
||||
"titel": "01 · Vorbereitung",
|
||||
"schritte": [
|
||||
{ "titel": "Gerät auspacken", "pflicht": true },
|
||||
{
|
||||
"titel": "Seriennummer erfassen",
|
||||
"hilfetext": "Aufkleber auf der Unterseite. **Nicht** die Artikelnummer.",
|
||||
"typ": "TEXT",
|
||||
"pflicht": true,
|
||||
"konfig": { "mehrzeilig": false },
|
||||
"dokuUrl": "https://intern.example/seriennummern"
|
||||
},
|
||||
{
|
||||
"titel": "Arbeitsspeicher prüfen",
|
||||
"typ": "ZAHL",
|
||||
"konfig": { "min": 8, "max": 64, "einheit": "GB" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"titel": "02 · Konto",
|
||||
"schritte": [
|
||||
{
|
||||
"titel": "Benutzerkonto anlegen",
|
||||
"typ": "BEFEHL",
|
||||
"pflicht": true,
|
||||
// Die Platzhalter werden erst beim Abarbeiten ersetzt; hier stehen
|
||||
// sie wörtlich.
|
||||
"konfig": {
|
||||
"shell": "powershell",
|
||||
"befehl": "New-ADUser -Name \"{{vorname}}\" -Department \"{{abteilung}}\""
|
||||
}
|
||||
},
|
||||
{ "titel": "Eintrittsdatum hinterlegen", "typ": "DATUM" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Felder
|
||||
|
||||
### Kopf
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------------- |
|
||||
| `format` | Pflicht | Fassung des Austauschformats. Derzeit `1`; ein anderer Wert wird abgewiesen. |
|
||||
| `name` | Pflicht | Name der Vorlage, nicht leer. Namen sind **nicht** eindeutig. |
|
||||
| `beschreibung` | optional | Ein bis zwei Sätze, was die Vorlage abdeckt. |
|
||||
| `kategorie` | optional | Freitext, gruppiert die Vorlagenliste (etwa `Onboarding`). |
|
||||
| `schlagworte` | optional | Liste nicht leerer Texte; Standard `[]`. |
|
||||
| `version` | optional | Versionsnummer der exportierten Quelle. Reine Information, siehe unten. |
|
||||
| `struktur` | Pflicht | Objekt mit `variablen` und `abschnitte`. |
|
||||
|
||||
### `struktur`
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| ------------ | -------- | ---------------------------------------------------------- |
|
||||
| `variablen` | optional | Liste von Variablen in Abfragereihenfolge; Standard `[]`. |
|
||||
| `abschnitte` | optional | Liste von Abschnitten in Anzeigereihenfolge; Standard `[]`. |
|
||||
|
||||
### Variable
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------- |
|
||||
| `schluessel` | Pflicht | Der Name im Platzhalter `{{schluessel}}`. Regeln siehe unten. |
|
||||
| `beschriftung` | Pflicht | Was im Startformular über dem Feld steht, nicht leer. |
|
||||
| `typ` | optional | `TEXT`, `ZAHL`, `AUSWAHL` oder `DATUM`; Standard `TEXT`. |
|
||||
| `optionen` | optional | Nur bei `AUSWAHL`: die Auswahlwerte in Anzeigereihenfolge; Standard `[]`. |
|
||||
| `pflicht` | optional | `true` verlangt eine Eingabe beim Start; Standard `false`. |
|
||||
|
||||
Ein Schlüssel darf in einem Dokument nur **einmal** vorkommen. Ein zweiter Eintrag mit demselben
|
||||
Schlüssel ist ein Fehler und kein stilles Überschreiben — sonst verschwände eine Variable, ohne
|
||||
dass es jemand merkt.
|
||||
|
||||
### Abschnitt
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| ---------- | -------- | ----------------------------------------------------------------------------- |
|
||||
| `titel` | Pflicht | Überschrift, nicht leer. Üblich mit zweistelliger Nummer: `01 · Vorbereitung`. |
|
||||
| `schritte` | optional | Liste von Schritten in Abarbeitungsreihenfolge; Standard `[]`. |
|
||||
|
||||
### Schritt
|
||||
|
||||
| Feld | Pflicht | Bedeutung |
|
||||
| ----------- | -------- | -------------------------------------------------------------------------------- |
|
||||
| `titel` | Pflicht | Was zu tun ist, nicht leer. Darf Platzhalter enthalten. |
|
||||
| `hilfetext` | optional | Markdown, in der Abarbeitungsansicht ausklappbar. **Kein rohes HTML.** |
|
||||
| `typ` | optional | Einer der Schritt-Typen unten; Standard `HAKEN`. |
|
||||
| `pflicht` | optional | Pflichtschritte müssen vor dem Abschluss erledigt oder ausdrücklich als nicht zutreffend markiert sein; Standard `false`. |
|
||||
| `konfig` | optional | Typabhängige Einzelheiten; Standard `{}`. Siehe unten. |
|
||||
| `dokuUrl` | optional | Link auf eine Anleitung. Muss eine gültige Adresse sein. |
|
||||
|
||||
## Schritt-Typen und ihre `konfig`
|
||||
|
||||
| Typ | Was der Techniker tut | Schlüssel in `konfig` |
|
||||
| --------- | ------------------------------ | -------------------------------------------------------------- |
|
||||
| `HAKEN` | abhaken | keine |
|
||||
| `TEXT` | Text eintragen | `mehrzeilig` (`true`/`false`) |
|
||||
| `ZAHL` | Zahl eintragen | `min`, `max` (Zahlen), `einheit` (Text, höchstens 40 Zeichen) |
|
||||
| `AUSWAHL` | aus einer Liste wählen | `optionen` (Liste nicht leerer Texte, in Anzeigereihenfolge) |
|
||||
| `DATUM` | Datum wählen | keine |
|
||||
| `BEFEHL` | Befehl kopieren und ausführen | `shell` (`powershell`, `bash` oder `cmd`), `befehl` (Text) |
|
||||
|
||||
`min` und `max` sind Hinweise: ein Wert außerhalb wird markiert, nicht blockiert.
|
||||
|
||||
`konfig` wird **nachsichtig** geprüft. Unbekannte Schlüssel bleiben erhalten — eine Datei aus einer
|
||||
späteren Fassung verliert hier nichts, auch wenn diese Fassung die Schlüssel nicht kennt. Was die
|
||||
Anwendung kennt, wird geprüft: `shell` etwa akzeptiert nur die drei genannten Werte.
|
||||
|
||||
Ein Schlüssel, der nicht zum Typ passt, ist kein Fehler; er wird schlicht nicht beachtet. Wer den
|
||||
Typ eines Schritts ändert, räumt die `konfig` also besser selbst auf.
|
||||
|
||||
## Platzhalter und Variablenschlüssel
|
||||
|
||||
Ein Variablenschlüssel muss der Form `^[a-z][a-z0-9_]{0,39}$` entsprechen: Beginn mit einem
|
||||
Kleinbuchstaben, danach Kleinbuchstaben, Ziffern und Unterstrich, höchstens 40 Zeichen.
|
||||
`vorname`, `ad_gruppe` und `raum2` sind in Ordnung; `Vorname`, `mit-strich`, `2te_wahl` und ein
|
||||
leerer Schlüssel nicht.
|
||||
|
||||
Die Strenge hat einen Grund: der Schlüssel steht als `{{schluessel}}` in Titeln, Hilfetexten und
|
||||
Befehlen. Ein Platzhalter wird genau mit diesem Muster wiedererkannt. Wäre `Vor-Name` erlaubt,
|
||||
müsste der Suchausdruck Groß- und Kleinschreibung, Striche und Leerzeichen auseinanderhalten — und
|
||||
`{{Vor-Name}}` in einem PowerShell-Befehl wäre nicht mehr von dessen eigener Syntax zu trennen.
|
||||
Deshalb: ein Zeichensatz, eine Regel, keine Ausnahmen.
|
||||
|
||||
Geschweifte Klammern dürfen Leerzeichen enthalten (`{{ vorname }}` wird erkannt). Neben den
|
||||
Variablen der Vorlage gibt es eingebaute Platzhalter — `{{generiertes_passwort}}` etwa —, die nicht
|
||||
im Austauschdokument stehen und auch nirgends gespeichert werden.
|
||||
|
||||
## Was beim Import passiert
|
||||
|
||||
1. Das Dokument wird gegen das Schema geprüft. Schlägt das fehl, wird **nichts** geschrieben.
|
||||
2. Es entsteht eine **neue Vorlage** — der Import schreibt nie in eine bestehende hinein.
|
||||
3. Die Vorlage bekommt genau eine Version: `version = 1`, Status **`ENTWURF`**. Eine eingespielte
|
||||
Vorlage wird bewusst nicht sofort veröffentlicht; der Redakteur sieht sie sich erst an,
|
||||
ergänzt vielleicht etwas und veröffentlicht dann. Erst danach lässt sich ein Lauf daran starten.
|
||||
4. `position` wird aus der Reihenfolge der Listen abgeleitet (0, 1, 2 …).
|
||||
5. Das mitgeschickte `version`-Feld wird nicht übernommen. Eine Vorlage, die in der Quelle auf
|
||||
Version 7 stand, fängt hier wieder bei 1 an — die Versionsgeschichte der Quelle wandert nicht mit.
|
||||
6. Der Vorlagenname kommt so an, wie er im Dokument steht. Gibt es ihn schon, ist das kein Fehler
|
||||
und es wird nichts angehängt: Vorlagennamen sind nicht eindeutig.
|
||||
7. Alles geschieht in einer Transaktion. Ein Fehler auf halbem Weg hinterlässt keine halbe Vorlage.
|
||||
8. Ein Protokolleintrag mit der Aktion `Vorlage importiert` hält fest, wer die Vorlage eingespielt hat.
|
||||
|
||||
Wird das Dokument abgewiesen, nennt die Meldung die **erste** Beanstandung samt Pfad, etwa:
|
||||
|
||||
```
|
||||
struktur.abschnitte.1.schritte.2.titel: Ein Schritt braucht einen Titel.
|
||||
```
|
||||
|
||||
## Wie sich das Format ändern darf
|
||||
|
||||
Das Feld `format` ist die Fassung des Formats, nicht die der Anwendung. Es gilt:
|
||||
|
||||
- **Neue optionale Felder** und neue `konfig`-Schlüssel erhöhen `format` **nicht**. Alte Dateien
|
||||
bleiben gültig, und weil `konfig` nachsichtig geprüft wird, überleben neue Schlüssel auch einen
|
||||
Rundlauf durch eine ältere Fassung.
|
||||
- **Neue Schritt- und Variablentypen** erhöhen `format` ebenfalls nicht. Eine Fassung, die den Typ
|
||||
nicht kennt, weist die Datei allerdings ab, weil der Typ eine geprüfte Aufzählung ist.
|
||||
- Erst eine Änderung, die alte Dateien **unbrauchbar** macht — ein umbenanntes Pflichtfeld, eine
|
||||
andere Verschachtelung —, erhöht `format` auf `2`.
|
||||
- Ein unbekannter `format`-Wert wird mit einer klaren Meldung abgewiesen, nie geraten. Ein Import,
|
||||
der die Hälfte versteht und die andere Hälfte still verwirft, wäre schlimmer als ein Fehlschlag.
|
||||
|
||||
Die Zahl steht als `FORMAT_VERSION` in `src/lib/vorlagen-format.ts` an genau einer Stelle.
|
||||
Reference in New Issue
Block a user