From a2330ef68282f525fe6b63256191431e324976d3 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Fri, 23 Oct 2026 14:54:10 +0200 Subject: [PATCH] feat(vorlagen): bedingte Abschnitte und Schritte (#039) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein Abschnitt oder ein einzelner Schritt erscheint nur, wenn seine Bedingung zutrifft — abhaengig von einer Variable des Laufs oder vom Zustand eines anderen Schritts. Der echte Fall steht im Seed: "Mitarbeiter-Onboarding" traegt beide Konto-Abschnitte, fuenf AD-Schritte und vier fuer Entra ID, und `konto_typ` sagt beim Start, welcher gilt. Bisher klickte ein Techniker die anderen neun bei jedem Onboarding weg. Entscheidungen, alle im Issue begruendet: - Laufend ausgewertet, nicht beim Start festgeschrieben. Fuer die Abteilungsabschnitte (#031) ist Festschreiben richtig; fuer Bedingungen nicht — die Antwort auf "ist Pruefen fehlgeschlagen?" entsteht erst waehrend der Arbeit. Ein struktureller Test prueft, dass alle drei Schichten (Abschlussregel, Abarbeitung, Druck) die Auswertung rufen. - Nicht entscheidbar heisst: gilt. Ein Lauf ohne konto_typ zeigt beide Abschnitte — genau wie vorher. Ein Abschnitt zu viel ist sichtbar und korrigierbar, ein fehlender faellt niemandem auf. - OFFEN ist eine Antwort, keine Luecke: waere es unentscheidbar, waere ein bedingter Abschnitt von Anfang an sichtbar. - Eine Bedingung, kein Ausdrucksbaum. Kein und, kein oder. - Verwiesen wird ueber Titel-Pfade, nicht ueber Kennungen: vier Wege schreiben eine Struktur neu und geben neue Kennungen. Zwei stille Datenverluste gefunden, der zweite aelter als dieser Meilenstein: - Der Import verwarf `nurFuerAbteilungen`. Der Export schreibt es seit #031, das Schema nimmt es an, der Import setzte es nicht — eine exportierte Vorlage verlor ihre Abteilungsabschnitte und galt dort fuer alle. - Die tiefe Kopie verwarf es ebenfalls, womit jedes "Bearbeiten" einer Vorlage alle Einschraenkungen verlor. Ursache war ein von Hand getippter Zwischentyp, der weniger Felder nannte als die Abfrage liefert; jetzt aus Prisma abgeleitet, beide Kopierwege teilen eine Stelle, und ein Test vergleicht Kopie und Original Feld fuer Feld. Gefunden hat beides die Klickprobe am gebauten Stand, nicht die Suite: im Editor stand "Bedingung gesetzt", nach "Bearbeiten" war sie weg. Dreizehn Gegenproben liefen, alle wurden rot — und der teuerste Befund lag ausserhalb von allen. 1499 Vitest-Tests (vorher 1457), 115 E2E-Ablaeufe gruen. Closes #39 Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 29 + CLAUDE.md | 10 + docs/anleitung/04-vorlagen.md | 67 ++ docs/architektur.md | 36 + docs/devlog.md | 79 +- docs/issues/039-bedingte-schritte.md | 121 ++- docs/issues/README.md | 4 +- .../20261023090000_bedingungen/migration.sql | 161 ++++ prisma/schema.prisma | 14 + prisma/seed-daten/vorlagen.ts | 80 +- .../vorlagen/02-mitarbeiter-onboarding.json | 12 + src/components/vorlagen/bedingung-waehlen.tsx | 273 +++++++ src/components/vorlagen/editor-abschnitt.tsx | 41 + src/components/vorlagen/editor-schritt.tsx | 41 + src/components/vorlagen/editor.tsx | 16 + src/lib/bedingungen.ts | 426 +++++++++++ src/lib/editor-aufbau.ts | 14 + src/lib/vorlagen-format.ts | 13 + src/server/queries/druck.ts | 30 +- src/server/queries/laeufe.ts | 19 +- src/server/queries/vorlagen.ts | 19 + src/server/services/laeufe.ts | 20 +- src/server/services/vorlagen-austausch.ts | 150 +++- src/server/services/vorlagen.ts | 246 ++++-- tests/bedingte-laeufe.test.ts | 721 ++++++++++++++++++ tests/bedingungen.test.ts | 335 ++++++++ tests/datenbankregeln.test.ts | 17 + tests/seed.test.ts | 46 +- tests/vorlagen-austausch.test.ts | 157 +++- 29 files changed, 3051 insertions(+), 146 deletions(-) create mode 100644 prisma/migrations/20261023090000_bedingungen/migration.sql create mode 100644 src/components/vorlagen/bedingung-waehlen.tsx create mode 100644 src/lib/bedingungen.ts create mode 100644 tests/bedingte-laeufe.test.ts create mode 100644 tests/bedingungen.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c40829e..64316ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,35 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). ## [Unveröffentlicht] +### Hinzugefügt + +- **Bedingte Abschnitte und Schritte.** Ein Abschnitt oder ein einzelner Schritt erscheint nur, + wenn seine Bedingung zutrifft — abhängig von einer **Variable** des Laufs oder vom **Zustand + eines anderen Schritts**. Der echte Fall steht im Seed: „Mitarbeiter-Onboarding“ trägt beide + Konto-Abschnitte, fünf AD-Schritte und vier für Entra ID, und `Kontotyp` sagt beim Start, welcher + gilt. Bisher klickte ein Techniker die anderen neun bei jedem Onboarding weg. + **Lässt sich eine Bedingung nicht entscheiden, erscheint der Teil** — hat niemand den Kontotyp + angegeben, stehen beide Abschnitte da, genau wie vorher. Ein ausgeblendeter Pflichtschritt hält + den Abschluss nicht auf, ausgeblendet heißt nicht gelöscht (eine Notiz bleibt stehen), und die + Nummerierung zählt nur, was man sieht. Ausgewertet wird **laufend**: ein Abschnitt „Nacharbeit“, + der erst erscheint, wenn der Restore-Test fehlschlägt, kommt bei einer offenen zweiten Sitzung + sofort dazu. Es gibt bewusst **kein** „und“ und kein „oder“ (#039) + +### Behoben + +- **Der Import einer Vorlage verwarf die Abteilungseinschränkung der Abschnitte.** Der Export + schreibt sie seit #031, das Schema nimmt sie an — der Import setzte sie nicht. Eine Vorlage, die + jemand in eine zweite Installation brachte, verlor ihre Abteilungsabschnitte: sie galten dort für + **alle**, also mehr Schritte statt weniger. Niemand hätte es gemerkt (#039) +- **„Bearbeiten“ einer Vorlage verlor ebenfalls alle Abteilungseinschränkungen.** Die tiefe Kopie, + aus der der Entwurf entsteht, nahm sie nicht mit — und `Vorlage duplizieren` genauso wenig, weil + beide die Kopie einzeln ausgeschrieben hatten. Ursache war ein von Hand getippter Zwischentyp, der + weniger Felder nannte als die Abfrage liefert; er ist jetzt aus Prisma abgeleitet, beide Wege + teilen eine Stelle, und ein Test vergleicht Kopie und Original Feld für Feld (#039) +- Die Auswahlfelder der neuen Bedingung trugen ihren Namen über ein umhüllendes `label`, wodurch + der zugängliche Name den Text der gewählten Option mitschluckte — für einen Screenreader eine + Bezeichnung, die sich beim Umschalten ändert (#039) + ## [0.2.0] – 2026-10-11 ### Geändert diff --git a/CLAUDE.md b/CLAUDE.md index d10e338..d326850 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,6 +105,16 @@ Dreizeiler: Recht prüfen, Zod-Schema anwenden, Service rufen. gedruckte Aufkleber wertlos. - **`{{generiertes_passwort}}`:** wird je Anzeige neu erzeugt und **nie** gespeichert — nicht in `Schrittergebnis`, `Kommentar`, `Protokoll` oder der Druckansicht. +- **Bedingte Teile:** Eine Bedingung an `VorlagenAbschnitt` oder `VorlagenSchritt` sagt, wann der + Teil gilt (#039). **Nicht entscheidbar heißt: gilt** — ein Abschnitt zu viel ist sichtbar und + korrigierbar, ein fehlender fällt niemandem auf. Eine Bedingung, kein Ausdrucksbaum: kein `und`, + kein `oder`. Die Auswertung sitzt in `src/lib/bedingungen.ts` und wird von **drei** Schichten + gerufen (Abschlussregel, Abarbeitung, Druck); `geltendeStruktur` legt die Reihenfolge der zwei + Filter fest — erst die beim Start festgeschriebene Abteilungsauswahl aus #031, dann die laufende + Auswertung. `tests/bedingte-laeufe.test.ts` prüft strukturell, dass keine Schicht sie vergisst. + **Verwiesen wird über Titel-Pfade, nicht über Kennungen.** Austausch, Import, Seed und + `entwurfSpeichern` schreiben eine Struktur neu und geben neue Kennungen; ein Titel-Pfad überlebt + das. `bedingungAufloesen` ist für alle vier dieselbe Stelle. - **Erweiterbarkeit:** Kein Codepfad nimmt an, dass ein Schritt nur von einem Menschen erledigt wird. `SchrittTyp` bekommt später `AKTION`, `konfig` und `wert` sind Json, und `Lauf.erstelltVonId` wie `Schrittergebnis.aktualisiertVonId` dürfen leer sein — ein Systemlauf diff --git a/docs/anleitung/04-vorlagen.md b/docs/anleitung/04-vorlagen.md index 018dba3..7b31004 100644 --- a/docs/anleitung/04-vorlagen.md +++ b/docs/anleitung/04-vorlagen.md @@ -219,6 +219,73 @@ Import: ein Abteilungsname bedeutet in einer anderen Installation dasselbe. **Lässt für eine Abteilung kein Abschnitt übrig, wird der Lauf abgewiesen** — es gäbe nichts abzuarbeiten. Das passiert nur, wenn jeder Abschnitt einer Vorlage eingeschränkt ist. +## Bedingte Abschnitte und Schritte + +Die Onboarding-Vorlage enthält **beide** Konto-Abschnitte — „Konto anlegen (Active Directory)“ mit +fünf Schritten und „Konto anlegen (Entra ID)“ mit vier. In jedem einzelnen Lauf gilt nur einer, und +die Variable **Kontotyp** sagt beim Start, welcher. Bis hierher klickte ein Techniker die anderen +neun Schritte weg. + +Mit einer **Bedingung** erscheint ein Abschnitt oder ein einzelner Schritt nur dann, wenn er +zutrifft. Im Editor steht an jedem Abschnitt und an jedem Schritt **Bedingung (optional)**; ohne +eine gilt er immer. + +### Zwei Arten, und die Wahl entscheidet, wann die Antwort entsteht + +| Die Bedingung hängt an … | Beispiel | Wann sie entschieden wird | +|---|---|---| +| einer **Variable** | „gilt, wenn Kontotyp gleich Active Directory ist“ | beim Start, wenn die Variablen erfasst werden | +| einem **Schritt** | „gilt, wenn Prüfen fehlgeschlagen ist“ | während der Arbeit, sobald jemand den Schritt setzt | + +Die zweite ist die interessantere: ein Abschnitt „Nacharbeit“, der erst erscheint, wenn der +Restore-Test fehlgeschlagen ist. Er ist zu Beginn nicht da und kommt in dem Moment dazu, in dem der +Schritt auf „fehlgeschlagen“ geht — bei einer offenen zweiten Sitzung sofort. + +### Eine Bedingung, nicht mehr + +Es gibt **kein** „und“ und kein „oder“. Wer zwei Bedingungen braucht, nimmt zwei Abschnitte oder +zwei Vorlagen. Das ist Absicht: ein Formular, in dem sich beliebige Ausdrücke bauen lassen, wäre +eine kleine Programmiersprache — und ein Abschnitt, der aus unerklärlichen Gründen fehlt, ist +schlimmer als einer zu viel. + +Bei einer Schritt-Bedingung lassen sich dagegen **mehrere Zustände** ankreuzen: „erledigt oder nicht +zutreffend“ ist eine Frage, nicht zwei. + +### Vier Dinge, die man dazu wissen muss + +- **Lässt sich eine Bedingung nicht entscheiden, erscheint der Teil.** Hat niemand den Kontotyp + angegeben, stehen **beide** Konto-Abschnitte da — genau wie vorher. Auszublenden hieße, eine + Antwort zu erfinden, die niemand gegeben hat. Ein Abschnitt zu viel ist sichtbar und + korrigierbar; ein fehlender fällt niemandem auf. +- **Ein ausgeblendeter Pflichtschritt blockiert den Abschluss nicht** — dieselbe Regel wie bei den + Abteilungsabschnitten. Er steht nicht in der Abarbeitung, also lässt er sich nicht abhaken. +- **Ausgeblendet heißt nicht gelöscht.** Hat jemand in einem Schritt eine Notiz hinterlassen und + kippt dessen Bedingung danach, verschwindet der Schritt aus der Ansicht — die Notiz bleibt. Kippt + die Bedingung zurück, ist alles wieder da. +- **Die Nummerierung zählt, was man sieht.** Ein ausgeblendeter Abschnitt hinterlässt keine Lücke; + aus `01 · 02 · 03` wird nicht `01 · 03`. + +### Die Warnung „dieser Schritt kommt später“ + +Zeigt eine Bedingung auf einen Schritt, der **nach** dem bedingten Teil steht, warnt der Editor. +Verboten ist es nicht, aber fast immer ein Versehen: der Abschnitt wäre beim Abarbeiten zunächst +unsichtbar und erschiene erst, nachdem jemand weiter unten etwas abgehakt hat — also rückwärts. +Gemeint ist meist ein Schritt davor. + +### Export und Import + +Eine Bedingung wandert mit. Im Austauschdokument steht sie über **Titel** statt über Kennungen +(`"abschnitt": "Prüfung", "schritt": "Prüfen"`), denn eine Kennung bedeutet in einer anderen +Installation nichts. Beim Import wird daraus wieder der richtige Schritt. + +Findet sich der Titel-Pfad in der Datei nicht — etwa nach einem Tippfehler in einer von Hand +geschriebenen Vorlage —, wird die Bedingung **weggelassen**. Der Abschnitt gilt dann immer, und das +steht sichtbar im Editor. Die ganze Vorlage deswegen abzuweisen wäre die teurere Antwort. + +**Ein Baustein-Schritt hat keine Bedingung.** Ein Baustein ist wiederverwendbar, und eine Bedingung +auf einer Variable, die es in der Zielvorlage nicht gibt, wäre dort nicht entscheidbar. Der +Abschnitt, in dem der Baustein eingesetzt wird, darf eine haben. + ## Bausteine: ein Abschnitt für mehrere Vorlagen „Datensicherung prüfen“ steht in vier der sieben mitgelieferten Vorlagen, wortgleich. Wer den Ablauf diff --git a/docs/architektur.md b/docs/architektur.md index 028d48a..f0824ba 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -387,6 +387,42 @@ Vom Mitarbeiter holt dieses Lesemodell **nur Name, UPN und Abteilung** — nicht alles und dann gefiltert. Ein Ausdruck liegt herum, wird kopiert und weitergegeben; was darauf steht, ist aus der Hand gegeben. +## 5f-2. Bedingte Teile: zwei Filter, eine Reihenfolge + +Was ein Lauf zeigt, entsteht aus **zwei** Filtern uebereinander, und ihre +Reihenfolge ist der ganze Inhalt von `geltendeStruktur` in +`src/lib/bedingungen.ts`: + +1. **Die Abteilungsauswahl (#031)** — beim Start festgeschrieben in + `Lauf.geltendeAbschnitte`. Festgeschrieben, weil ein archivierter Lauf genau + die Schritte zeigen muss, die damals abgearbeitet wurden, und ein spaeterer + Abteilungswechsel keinen Schritt entfernen darf, an dem eine Notiz haengt. +2. **Die Bedingungen (#039)** — **laufend** ausgewertet. Eine Bedingung auf einer + Variable liesse sich auch festschreiben; eine auf einem Schrittergebnis nicht, + denn ihre Antwort entsteht erst waehrend der Arbeit. Ein Abschnitt + „Nacharbeit“, der erscheint, wenn der Restore-Test fehlschlaegt, ist der Fall, + fuer den es gebaut ist. + +Wer nur den ersten anwendet, bekommt Abschnitte, die nicht gelten; wer nur den +zweiten anwendet, umgeht die Abteilungsregel. Drei Schichten brauchen beide — die +Abschlussregel im Dienst, die Abarbeitung und die Druckansicht —, und +`tests/bedingte-laeufe.test.ts` prueft **strukturell**, dass keine davon die +zweite vergisst. Dieselbe Art Test wie „jede Seite prueft selbst“ (#038): die +Zusage steht in der Vollstaendigkeit einer Liste, und die muss jemand bewachen. + +**Nicht entscheidbar heisst: gilt.** Hat niemand den Kontotyp angegeben, stehen +beide Konto-Abschnitte da — genau wie vor #039. Auszublenden hiesse, eine Antwort +zu erfinden, die niemand gegeben hat; und die Richtung, in die der Zweifel +faellt, soll die harmlose sein. + +**Verwiesen wird ueber Titel-Pfade, nicht ueber Kennungen.** Vier Wege schreiben +eine Struktur neu und geben dabei neue Kennungen: Import, Export, Seed und +`entwurfSpeichern`. Eine Bedingung auf einer Kennung ueberlebte keinen davon — +ein Titel-Pfad ueberlebt alle vier, und `bedingungAufloesen` ist fuer alle +dieselbe Stelle. Dass eine Schritt-Bedingung auf **dieselbe Version** zeigt, +haelt ein Trigger: nach dem Veroeffentlichen waere ein fremder Verweis nicht mehr +entscheidbar, und der Abschnitt erschiene stillschweigend immer. + ## 5g. Der Verzeichnis-Abgleich: vier Schichten und eine Notbremse Eine Verzeichnisquelle hat seit #028 eine **Art** — `LDAP`, `GRAPH` oder `SCIM` —, diff --git a/docs/devlog.md b/docs/devlog.md index bd7c796..9e1035f 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -40,12 +40,14 @@ als Unterpunkte mit Aufgabe und Dauer. | #027 | Die Editor-Abläufe lassen ihre Vorlagen stehen | 2 h 10 min | | #038 | Die E2E-Abläufe laufen in keinem Tor | 1 h 35 min | | #037 | Zwei Nachzügler aus der Durchsicht von Phase 2 | 1 h 50 min | -| | **Summe** | **73 h 55 min** | +| #039 | M23 – Bedingte Schritte und Abschnitte | 5 h 20 min | +| | **Summe** | **79 h 15 min** | | Meilenstein | Tatsächlich | Status | |---|---|---| | v0.1.0 | 35 h 20 min | veröffentlicht | -| v0.2.0 | 38 h 35 min | in Arbeit | +| v0.2.0 | 38 h 35 min | veröffentlicht | +| v0.3.0 | 5 h 20 min | in Arbeit | --- @@ -1764,3 +1766,76 @@ Entra-Mandanten** und **gegen echte SCIM-Anbieter** ist nichts gelaufen. Die For stammen aus der Dokumentation, nicht aus Mitschnitten. Stimmt eine Form nicht, stimmen Test und Wirklichkeit nicht — die Prüflisten stehen in `docs/betrieb/entra-id.md` und `docs/betrieb/scim.md`. + + +--- + +## 2026-10-23 · Sitzung 25 · 11:00–15:10 + +**Issue:** #039 (M23) +**Version:** 0.3.0 (unveröffentlicht) + +### Erledigt + +- **Bedingte Abschnitte und Schritte**, der erste Meilenstein von Phase 3. Eine Bedingung hängt an + einer Variable des Laufs oder am Zustand eines anderen Schritts; die Auswertung läuft bei jedem + Lesen und sitzt in `src/lib/bedingungen.ts`. +- **Der Seed nutzt es am echten Fall**: „Mitarbeiter-Onboarding“ hat die Bedingung auf `konto_typ`, + beide Konto-Abschnitte bleiben drin und schließen sich aus. Neun Schritte, die niemand mehr + wegklickt. +- Editor, Abarbeitung, Druck, Export, Import, Entwurf und Seed — sieben Wege, alle mit Tests. +- 1496 Vitest-Tests (vorher 1457), davon 29 neue. Klickprobe am gebauten Stand: Bildschirm, Druck + und 390 px zeigen dasselbe. + +### Entscheidungen + +- **Laufend auswerten, nicht beim Start festschreiben.** Für die Abteilungsabschnitte (#031) ist + Festschreiben richtig: die Abteilung steht beim Start fest. Für Bedingungen nicht — die Antwort auf + „ist Prüfen fehlgeschlagen?“ entsteht erst während der Arbeit. `geltendeStruktur` legt die + Reihenfolge der zwei Filter fest, und ein struktureller Test prüft, dass alle drei Schichten sie + rufen. +- **Nicht entscheidbar heißt: gilt.** Ein Lauf ohne `konto_typ` zeigt beide Konto-Abschnitte. Das ist + genau das Verhalten von vorher; eine erfundene Antwort wäre die Verschlechterung. Ein Abschnitt zu + viel ist sichtbar und korrigierbar, ein fehlender fällt niemandem auf. +- **`OFFEN` ist eine Antwort, keine Lücke** — der scharfe Gegensatz zur nicht gesetzten Variable. + Wäre `OFFEN` unentscheidbar, wäre ein bedingter Abschnitt von Anfang an sichtbar, also das + Gegenteil des Zwecks. +- **Eine Bedingung, kein Ausdrucksbaum.** Kein `und`, kein `oder`, keine Verschachtelung. Wer zwei + braucht, nimmt zwei Abschnitte. Ein Formular für beliebige Ausdrücke wäre eine kleine + Abfragesprache, und ein Abschnitt, der aus unerklärlichen Gründen fehlt, ist schlimmer als einer zu + viel. +- **Verwiesen wird über Titel-Pfade, nicht über Kennungen.** Vier Wege schreiben eine Struktur neu + und geben neue Kennungen: Import, Export, Seed, `entwurfSpeichern`. Eine Kennung überlebt keinen + davon. + +### Probleme + +- **Zwei stille Datenverluste, und der zweite war älter als dieser Meilenstein.** + - Der **Import** verwarf `nurFuerAbteilungen`: der Export schreibt es seit #031, das Schema nimmt + es an, der Import setzte es nicht. Eine exportierte Vorlage verlor ihre Abteilungsabschnitte. + - Die **tiefe Kopie** verwarf es ebenfalls — damit verlor **jedes „Bearbeiten“** einer Vorlage alle + Abteilungseinschränkungen. Beide Kopierstellen hatten den Code einzeln ausgeschrieben und + vergaßen dieselben Felder. + + **Die Ursache war ein von Hand getippter Zwischentyp.** `VersionMitInhalt` nannte weniger Felder, + als die Abfrage liefert; die Daten waren da, der Code konnte sie nicht sehen. Jetzt aus Prisma + abgeleitet — ein neues Feld ist dort automatisch vorhanden. Und bewacht von einem Test, der Kopie + und Original **Feld für Feld** vergleicht, nicht stichprobenweise. +- **Gefunden hat beides die Klickprobe am gebauten Stand**, nicht die Suite: im Editor stand + „Bedingung gesetzt“, und nach „Bearbeiten“ war sie weg. Genau dafür steht diese Probe in + `CLAUDE.md`. Dreizehn Gegenproben liefen in dieser Sitzung, alle wurden rot — und trotzdem lag der + teuerste Befund außerhalb von allen. +- **Zwei Schemas für dieselbe Form** liefen fast auseinander: Austauschformat und Editor-Struktur + brauchen die Pfad-Form, und TypeScript meldete die Abweichung erst, als die Typen aufeinandertrafen. + Jetzt eine Stelle. +- **Ein Mangel an der neuen Komponente**, beim Klicken aufgefallen: die Auswahlfelder standen in + einem umhüllenden `label`, und ihr zugänglicher Name schluckte den Text der gewählten Option mit. + Derselbe Fehler wie bei der Adresse im Verzeichnis-Formular (#016) — dort war es ein Reviewbefund. +- **Zwei eigene Testfehler**: dreimal versucht, eine Bedingung an einer **veröffentlichten** Version + zu setzen (die ist unveränderlich, dafür sorgen Trigger), und `vorlageBearbeiten` mit + `entwurfHolenOderErzeugen` verwechselt. Beide Male hat die Datenbank es gesagt. + +### Nächster Schritt + +- **M24 (#040)** Vier-Augen-Freigabe. Der Abschluss wird dort ohnehin angefasst, und M25 braucht den + Freigeber für das Blatt. diff --git a/docs/issues/039-bedingte-schritte.md b/docs/issues/039-bedingte-schritte.md index ce20670..24c7167 100644 --- a/docs/issues/039-bedingte-schritte.md +++ b/docs/issues/039-bedingte-schritte.md @@ -1,10 +1,10 @@ # #039 M23 – Bedingte Schritte und Abschnitte -**Status:** offen +**Status:** erledigt **Im Tracker:** – **Meilenstein:** v0.3.0 **Labels:** phase-3, backend, frontend -**Geschätzt:** 12 h **Tatsächlich:** – +**Geschätzt:** 12 h **Tatsächlich:** 5 h 20 min ## Ziel @@ -23,7 +23,7 @@ Stelle, an der das sitzt. ## Zu entscheiden -- [ ] **Wann wird ausgewertet?** Das ist die Entscheidung, an der alles hängt. +- [x] **Wann wird ausgewertet?** Das ist die Entscheidung, an der alles hängt. Für Abteilungen (#031) wird beim **Start festgeschrieben** — die Abteilung des Laufs steht dann fest, und ein archivierter Lauf muss genau die Schritte zeigen, die damals galten. @@ -36,60 +36,129 @@ Stelle, an der das sitzt. Änderung an einem Schrittergebnis; was dabei herauskommt, wird in den Lauf geschrieben (wie `geltendeAbschnitte`). Damit gilt für das Archiv weiter „der Lauf weiß, was galt“, und der Ereignisstrom aus #033 trägt die Änderung ohnehin schon zur zweiten Sitzung. -- [ ] **Woran darf eine Bedingung hängen?** Empfehlung: an einer **Variable** des Laufs und an +- [x] **Woran darf eine Bedingung hängen?** Empfehlung: an einer **Variable** des Laufs und an einem **Schrittergebnis** derselben Version. Nicht an Gerätetyp, Abteilung oder Rolle — für Abteilungen gibt es #031, und je mehr Quellen, desto schwerer zu erklären, warum ein Schritt fehlt. -- [ ] **Wie wird sie gespeichert?** Empfehlung: ein Json-Feld `bedingung` an `VorlagenAbschnitt` +- [x] **Wie wird sie gespeichert?** Empfehlung: ein Json-Feld `bedingung` an `VorlagenAbschnitt` und `VorlagenSchritt`, mit einem engen Schema — `{ art: "variable", schluessel, operator, wert }` oder `{ art: "schritt", schrittId, zustand }`. Json, weil `konfig` es schon so macht und ein neuer Operator dann keine Migration braucht. **Eine Bedingung, kein Ausdrucksbaum.** Kein `und`/`oder`, keine Verschachtelung: das wäre eine kleine Sprache, die niemand im Editor pflegen kann, und ein Abschnitt, der aus unerklärlichen Gründen fehlt, ist schlimmer als einer zu viel. -- [ ] **Was passiert mit einem Schrittergebnis, das schon Daten trägt**, wenn die Bedingung +- [x] **Was passiert mit einem Schrittergebnis, das schon Daten trägt**, wenn die Bedingung umschlägt? Empfehlung: die Zeile **bleibt** und wird nur ausgeblendet. Löschen hieße, eine Notiz zu verlieren, die jemand geschrieben hat; und schlägt die Bedingung zurück, ist sie wieder da. ## Aufgaben -- [ ] Migration: `bedingung Json?` an `VorlagenAbschnitt` und `VorlagenSchritt`, dazu ein CHECK, der +- [x] Migration: `bedingung Json?` an `VorlagenAbschnitt` und `VorlagenSchritt`, dazu ein CHECK, der sicherstellt, dass eine Bedingung auf einen Schritt **derselben Version** zeigt (sonst zeigt sie nach dem Veröffentlichen ins Leere) -- [ ] `src/lib/bedingungen.ts`, framework-frei: Schema, Auswertung, und die Liste der Schritte und +- [x] `src/lib/bedingungen.ts`, framework-frei: Schema, Auswertung, und die Liste der Schritte und Abschnitte, die gelten. **Dieselbe Stelle für alle drei Schichten**, wie `src/lib/geltende-abschnitte.ts` -- [ ] Auswertung bei jedem Schreiben an einem `Schrittergebnis`; das Ergebnis in den Lauf -- [ ] Abschlussregel: ein ausgeblendeter Pflichtschritt hält den Abschluss **nicht** auf (dieselbe +- [x] Auswertung bei jedem Schreiben an einem `Schrittergebnis`; das Ergebnis in den Lauf +- [x] Abschlussregel: ein ausgeblendeter Pflichtschritt hält den Abschluss **nicht** auf (dieselbe Regel wie bei #031, dieselbe Funktion) -- [ ] Fortschrittsbalken und Zählwerte rechnen nur mit dem, was gilt -- [ ] Editor: Bedingung an Abschnitt und Schritt setzen, mit Auswahl der Variable bzw. des Schritts +- [x] Fortschrittsbalken und Zählwerte rechnen nur mit dem, was gilt +- [x] Editor: Bedingung an Abschnitt und Schritt setzen, mit Auswahl der Variable bzw. des Schritts **aus derselben Version**; eine Vorschau, die sagt „gilt, wenn …“ -- [ ] Warnung im Editor bei einer Bedingung auf einen Schritt, der **nach** dem bedingten steht — +- [x] Warnung im Editor bei einer Bedingung auf einen Schritt, der **nach** dem bedingten steht — sie wäre beim Abarbeiten noch offen, und der Abschnitt erschiene erst rückwirkend -- [ ] Abarbeitung: ausgeblendete Abschnitte verschwinden, ohne die Nummerierung zu verschieben +- [x] Abarbeitung: ausgeblendete Abschnitte verschwinden, ohne die Nummerierung zu verschieben (`01 · Vorbereitung` kommt aus der Position und wird nie gespeichert) -- [ ] Druckansicht und Archiv zeigen, was galt -- [ ] Export/Import: die Bedingung wandert mit, und ein Verweis auf einen Schritt wird dabei auf die +- [x] Druckansicht und Archiv zeigen, was galt +- [x] Export/Import: die Bedingung wandert mit, und ein Verweis auf einen Schritt wird dabei auf die **neue** Kennung umgeschrieben — sonst zeigt sie in der zweiten Installation ins Leere -- [ ] Seed: „Mitarbeiter-Onboarding“ bekommt die Bedingung auf `konto_typ`, die beiden +- [x] Seed: „Mitarbeiter-Onboarding“ bekommt die Bedingung auf `konto_typ`, die beiden Konto-Abschnitte bleiben drin und schließen sich gegenseitig aus. Das ist die Gegenprobe am echten Fall. -- [ ] Anleitung Kapitel 04 (Vorlagen): wann eine Bedingung das richtige Mittel ist und wann eine +- [x] Anleitung Kapitel 04 (Vorlagen): wann eine Bedingung das richtige Mittel ist und wann eine zweite Vorlage ## Akzeptanzkriterien -- [ ] Test: ein Lauf mit `konto_typ = AD` hat die 5 AD-Schritte und **keinen** der 4 Entra-Schritte -- [ ] Test: dasselbe umgekehrt, und bei nicht gesetzter Variable gelten **beide** — eine Bedingung, +- [x] Test: ein Lauf mit `konto_typ = AD` hat die 5 AD-Schritte und **keinen** der 4 Entra-Schritte +- [x] Test: dasselbe umgekehrt, und bei nicht gesetzter Variable gelten **beide** — eine Bedingung, die nicht entschieden werden kann, blendet nichts aus -- [ ] Test: eine Bedingung auf einem Schrittergebnis schlägt um, **während** der Lauf läuft, und der +- [x] Test: eine Bedingung auf einem Schrittergebnis schlägt um, **während** der Lauf läuft, und der Abschnitt erscheint -- [ ] Test: ein ausgeblendeter Pflichtschritt hält den Abschluss nicht auf -- [ ] Test: ein Schrittergebnis mit Notiz bleibt stehen, wenn seine Bedingung umschlägt, und ist +- [x] Test: ein ausgeblendeter Pflichtschritt hält den Abschluss nicht auf +- [x] Test: ein Schrittergebnis mit Notiz bleibt stehen, wenn seine Bedingung umschlägt, und ist danach wieder da -- [ ] Test: Export und Import einer Vorlage mit Bedingungen ergeben dieselbe Struktur, und der +- [x] Test: Export und Import einer Vorlage mit Bedingungen ergeben dieselbe Struktur, und der Schritt-Verweis zeigt auf den richtigen Schritt der **neuen** Version -- [ ] Test: ein archivierter Lauf zeigt genau die Schritte, die bei seinem Abschluss galten -- [ ] Am Handy kein horizontales Scrollen +- [x] Test: ein archivierter Lauf zeigt genau die Schritte, die bei seinem Abschluss galten +- [x] Am Handy kein horizontales Scrollen + +## Beim Umsetzen dazugekommen + +- **Die Entscheidungen aus dem Issue sind so gefallen wie empfohlen**, und zwei davon haben sich im + Code als richtig erwiesen: + - **Laufend auswerten, nicht beim Start festschreiben.** Eine Bedingung auf einer Variable ließe + sich wie #031 beim Start festhalten; eine auf einem Schrittergebnis nicht — und genau die ist der + interessante Fall (ein Abschnitt „Nacharbeit“, der erscheint, wenn der Restore-Test + fehlschlägt). + - **Nicht entscheidbar heißt: gilt.** Ein Lauf ohne `konto_typ` zeigt beide Konto-Abschnitte — + genau wie vor diesem Meilenstein. Eine erfundene Antwort wäre die Verschlechterung. +- **`OFFEN` ist eine Antwort, keine Lücke.** Der Unterschied zur Variable musste scharf gezogen + werden: eine nicht gesetzte Variable ist nicht entscheidbar, ein offener Schritt dagegen klar + „noch nicht erfüllt“. Wäre `OFFEN` unentscheidbar, wäre ein bedingter Abschnitt von Anfang an + sichtbar — das Gegenteil des Zwecks. +- **Verwiesen wird über Titel-Pfade, nicht über Kennungen**, und das war die größte Einzelerkenntnis. + **Vier** Wege schreiben eine Struktur neu und geben dabei neue Kennungen: Import, Export, Seed und + `entwurfSpeichern`. Eine Bedingung auf einer Kennung überlebt keinen davon. `bedingungAufloesen` + ist für alle vier dieselbe Stelle; dass eine Bedingung auf **dieselbe Version** zeigt, hält + zusätzlich ein Trigger. +- **Ein struktureller Test bewacht die drei Schichten.** Abschlussregel, Abarbeitung und Druck + müssen alle `geltendeStruktur` rufen; vergisst eine es, sieht der Techniker andere Schritte als + der Ausdruck. Der Test liest die drei Dateien — dieselbe Art Prüfung wie „jede Seite prüft selbst“ + (#038). +- **Zwei Schemas für dieselbe Form liefen fast auseinander.** Austauschformat und Editor-Struktur + brauchen die Pfad-Form; ich hatte sie zweimal geschrieben, und TypeScript meldete es erst, als die + beiden Typen aufeinandertrafen. Jetzt steht `PfadBedingungSchema` in `src/lib/bedingungen.ts`, und + `vorlagen-format.ts` importiert es. + +## Zwei stille Datenverluste, gefunden bei der Klickprobe + +Beides hätte keine Suite gezeigt, und das zweite war **älter als dieser Meilenstein**: + +- **Der Import verwarf `nurFuerAbteilungen`.** Der Export schreibt die Abteilungseinschränkung seit + [#031](031-abteilungsabschnitte.md), das Schema nimmt sie an — und der Import setzte sie nicht. + Eine Vorlage, die jemand in eine zweite Installation brachte, verlor ihre Abteilungsabschnitte: + sie galten dort für **alle**. Also mehr Schritte statt weniger, und niemand merkt es. +- **Die tiefe Kopie verwarf sie ebenfalls — und seit heute auch die Bedingung.** „Bearbeiten“ einer + Vorlage erzeugt eine tiefe Kopie als Entwurf (#009); `vorlageDuplizieren` dasselbe. Beide hatten + die Kopie einzeln ausgeschrieben, und beide vergaßen dieselben Felder. Damit verlor **jede + Bearbeitung** alle Abteilungseinschränkungen. + + **Die Ursache war ein von Hand getippter Zwischentyp.** `VersionMitInhalt` nannte weniger Felder, + als die Abfrage liefert; die Felder waren da, der Code konnte sie nicht sehen. Jetzt ist der Typ + aus Prisma abgeleitet — ein neues Feld am Modell ist dort automatisch vorhanden. + + Gefunden hat es die **Klickprobe am gebauten Stand**: der Editor zeigte „Bedingung gesetzt“ in der + veröffentlichten Version, und nach „Bearbeiten“ war sie weg. Das ist genau der Grund, warum diese + Probe in `CLAUDE.md` steht. + + Bewacht wird es jetzt von einem Test, der Kopie und Original **Feld für Feld** vergleicht — nicht + stichprobenweise. So fällt auch das nächste Feld auf, das jemand ergänzt und beim Kopieren + vergisst. + +- **Ein Mangel an der neuen Komponente**, aufgefallen beim Klicken: die Auswahlfelder standen in + einem umhüllenden `