diff --git a/CHANGELOG.md b/CHANGELOG.md index 25424dd..c40829e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,15 +44,15 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). - Der Healthcheck `/api/gesundheit` nennt im Fehlerfall keinen Grund mehr. Der Pfad ist ohne Anmeldung erreichbar, und eine Meldung von Prisma oder `pg` nennt Host, Port, Benutzernamen und Datenbanknamen. Nach außen steht jetzt nur noch `{"status":"fehler"}` mit Status 503; die - Begründung geht ins Protokoll des Prozesses (#039) + Begründung geht ins Protokoll des Prozesses (#025) - Die Konvention „nie auf rohe Hexwerte“ nennt ihre sechs begründeten Ausnahmen beim Namen — Druckansichten, QR-Code und `themeColor` entscheiden nicht nach unserem Farbschema. Die Regel war - strenger formuliert als gemeint (#040) + strenger formuliert als gemeint (#026) - `CLAUDE.md` und `README.md` nennen den LDAP-Testcontainer als Teil des Tors vor dem Commit. Ohne ihn überspringt die Suite **22 Tests** — den ganzen Verzeichnis-Abgleich —, sagt das auf stderr, - und der Lauf sieht trotzdem grün aus (#040) + und der Lauf sieht trotzdem grün aus (#026) - Im README fehlte `format:pruefen` in den Qualitätsschranken, obwohl `CLAUDE.md` es seit #010 - verlangt (#040) + verlangt (#026) ### Behoben @@ -235,9 +235,9 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). eingespielte Kopie bekommt **keinen** Schlüssel: sie soll nicht den Platz des Originals einnehmen (#021) - `pnpm pruefe:doku` vergleicht die Schranken-Befehle in `CLAUDE.md` und `README.md` und verlangt von - beiden den LDAP-Container. Genau diese Abweichung war unbemerkt entstanden (#040) + beiden den LDAP-Container. Genau diese Abweichung war unbemerkt entstanden (#026) - Fünf Tests für den Healthcheck, darunter einer, der belegt, dass die Begründung ins Protokoll - geschrieben und nicht bloß verschluckt wird (#039) + geschrieben und nicht bloß verschluckt wird (#025) ## [0.1.0] – 2026-09-26 diff --git a/CLAUDE.md b/CLAUDE.md index 47186b0..d10e338 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -257,7 +257,7 @@ geprüft. In CI bricht sie stattdessen ab, absichtlich. | `mailpit` | **3 Tests** (nachgezählt: 36 statt 33) | der echte SMTP-Versand in `tests/benachrichtigungen.test.ts` — die vier Anlässe, die Passwort-Prüfung über alle Rumpftexte und „nichts zweimal“ | `ldap` fiel bei der Abnahme zu #019 auf, `mailpit` kam mit #034 dazu. Beide Zahlen sind **gemessen**, -nicht geschätzt — in #040 hätte beinahe eine geratene in dieser Datei gestanden. +nicht geschätzt — in #026 hätte beinahe eine geratene in dieser Datei gestanden. `format:pruefen` gehört dazu, seit der Review in #010 darauf gestoßen ist: `lint-staged` formatiert beim Commit automatisch nach, und das Ergebnis wäre ein Commit mit Änderungen, die niemand gesehen diff --git a/docs/issues/003-epic-phase-3.md b/docs/issues/003-epic-phase-3.md index d177867..fa41a36 100644 --- a/docs/issues/003-epic-phase-3.md +++ b/docs/issues/003-epic-phase-3.md @@ -11,10 +11,15 @@ Nicht im MVP umsetzen. Hier gesammelt, damit das Datenmodell nichts verbaut. ## Aufgaben -- [ ] PDF-Übergabeprotokoll mit Unterschrift -- [ ] Bedingte Schritte und Abschnitte -- [ ] Vier-Augen-Freigabe -- [ ] Weitere Schritt-Typen +Geschnitten am 2026-10-11, nach dem Tag von v0.2.0. Vier Meilensteine, 34 h geschätzt; die +Reihenfolge ist nicht beliebig. + +| | Issue | Warum an dieser Stelle | +|---|---|---| +| M23 | [Bedingte Schritte und Abschnitte](039-bedingte-schritte.md) | zuerst, weil es als einziges am Datenmodell und an der Abschlussregel arbeitet — und weil der echte Fall im Seed steht: „Mitarbeiter-Onboarding“ trägt beide Konto-Abschnitte | +| M24 | [Vier-Augen-Freigabe](040-vier-augen-freigabe.md) | danach, weil der Abschluss dann einmal angefasst ist | +| M25 | [PDF-Übergabeprotokoll](041-pdf-uebergabeprotokoll.md) | nach M24, damit das Blatt den Freigeber nennen kann | +| M26 | [Weitere Schritt-Typen](042-weitere-schritt-typen.md) | zuletzt, weil `UNTERSCHRIFT` erst im PDF einen Zweck hat | ## Vorleistung im MVP diff --git a/docs/issues/039-bedingte-schritte.md b/docs/issues/039-bedingte-schritte.md new file mode 100644 index 0000000..ce20670 --- /dev/null +++ b/docs/issues/039-bedingte-schritte.md @@ -0,0 +1,95 @@ +# #039 M23 – Bedingte Schritte und Abschnitte + +**Status:** offen +**Im Tracker:** – +**Meilenstein:** v0.3.0 +**Labels:** phase-3, backend, frontend +**Geschätzt:** 12 h **Tatsächlich:** – + +## Ziel + +Eine Checkliste enthält heute **alles**, und was nicht zutrifft, wird von Hand auf +`NICHT_ZUTREFFEND` gesetzt. Der echte Fall steht im Seed: „Mitarbeiter-Onboarding“ trägt **beide** +Konto-Abschnitte — „Konto anlegen (Active Directory)“ mit 5 Schritten und „Konto anlegen (Entra +ID)“ mit 4 —, obwohl in jedem einzelnen Lauf nur einer gilt. Die Variable `konto_typ` sagt es +bereits; niemand wertet sie aus. + +Neun Schritte, die ein Techniker bei jedem Onboarding wegklickt. Die Abschnitts-Aktion aus #013 +macht es erträglich, aber sie ist ein Pflaster: der Fortschrittsbalken zählt die Schritte mit, und +im Archiv steht später eine Checkliste mit einem Abschnitt, den niemand je gebraucht hat. + +Setzt [#031](031-abteilungsabschnitte.md) voraus: `geltendeAbschnitte` und `nurGeltende` sind die +Stelle, an der das sitzt. + +## Zu entscheiden + +- [ ] **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. + + Für Bedingungen reicht das **nicht**: eine Bedingung auf einer Variable (`konto_typ`) ließe + sich beim Start auswerten, eine Bedingung auf einem **Schrittergebnis** („wenn Schritt 3 = + nein, dann Abschnitt B“) nicht — die Antwort entsteht erst während der Arbeit. + + Empfehlung: **laufend auswerten, Ergebnis mitschreiben.** Die Auswertung läuft bei jeder + Ä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 + 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` + 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 + 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 + 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 + 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 + 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 + **aus derselben Version**; eine Vorschau, die sagt „gilt, wenn …“ +- [ ] 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 + (`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 + **neue** Kennung umgeschrieben — sonst zeigt sie in der zweiten Installation ins Leere +- [ ] 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 + 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, + 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 + 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 + danach wieder da +- [ ] 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 diff --git a/docs/issues/040-vier-augen-freigabe.md b/docs/issues/040-vier-augen-freigabe.md new file mode 100644 index 0000000..bf1c245 --- /dev/null +++ b/docs/issues/040-vier-augen-freigabe.md @@ -0,0 +1,68 @@ +# #040 M24 – Vier-Augen-Freigabe + +**Status:** offen +**Im Tracker:** – +**Meilenstein:** v0.3.0 +**Labels:** phase-3, backend, frontend +**Geschätzt:** 8 h **Tatsächlich:** – + +## Ziel + +Manche Arbeiten soll nicht derselbe abschließen, der sie gemacht hat: ein Firewall-Rollout, ein +Restore-Test, das Löschen eines Kontos. Heute kann jeder `TECHNIKER` jeden Lauf abschließen und +archivieren. + +Gebraucht wird: eine Vorlage kann sagen „dieser Lauf braucht eine Freigabe“, und dann schließt ihn +ein **Zweiter** ab — nachweisbar im Protokoll. + +## Zu entscheiden + +- [ ] **Freigabe am Lauf oder am Schritt?** Empfehlung: **am Lauf**, gesetzt von der Vorlage. + Am Schritt wäre feiner und verführt zu Checklisten, in denen an fünf Stellen jemand warten + muss; am Lauf ist es eine Aussage über die Arbeit als Ganzes und erklärt sich von selbst. + Schritt-Freigaben bleiben möglich, wenn sich der Bedarf zeigt — `VorlagenSchritt.konfig` ist + ein Json. +- [ ] **Wer darf freigeben?** Empfehlung: ein eigenes Recht `freigeben`, standardmäßig bei `ADMIN` + und `REDAKTEUR` — **nicht** bei `TECHNIKER`. Begründung: die Vier-Augen-Regel soll nicht + davon abhängen, dass zufällig ein zweiter Techniker greifbar ist; und wer sie umgehen will, + braucht dann ein zweites Konto mit mehr Rechten, was im Protokoll steht. +- [ ] **Darf der Bearbeiter sich selbst freigeben?** **Nein**, und das ist der ganze Sinn. Geprüft + wird gegen `Schrittergebnis.aktualisiertVonId` **und** `Lauf.erstelltVonId`: wer irgendetwas + daran getan hat, gibt nicht frei. Ein `ADMIN`, der allein im Haus ist, kommt damit nicht + weiter — das ist eine bewusste Härte, und die Anleitung sagt es. +- [ ] **Was, wenn die Freigabe verweigert wird?** Empfehlung: ein Zustand „zurückgewiesen“ mit + Pflichtnotiz. Der Lauf geht zurück auf `IN_ARBEIT`, die Notiz steht im Protokoll und an der + Abarbeitungsansicht. Kein eigener `LaufStatus` — das wäre eine Änderung an einem Enum, das + vier Jahre halten soll; es genügt ein Feld plus Protokolleintrag. + +## Aufgaben + +- [ ] Migration: `freigabePflicht Boolean` an `VorlagenVersion`, dazu `freigegebenVonId`, + `freigegebenAm` und `freigabeNotiz` am `Lauf`; CHECK, dass `freigegebenVonId` und + `freigegebenAm` zusammen gesetzt sind (wie bei `abgeschlossenAm`) +- [ ] CHECK: `freigegebenVonId` ist **nicht** `erstelltVonId` — die Regel gehört in die Datenbank, + nicht nur in den Dienst (CLAUDE.md) +- [ ] Recht `freigeben` in `RECHTE`, Rollenmatrix-Test erweitern +- [ ] Dienst: `laufFreigeben(laufId, benutzer, notiz?)` und `laufZurueckweisen(laufId, benutzer, + notiz)`; Abschluss verlangt die Freigabe, wenn die Version sie fordert +- [ ] Die Prüfung „wer hat daran gearbeitet“ an **einer** Stelle, wie `laufMussVeraenderbarSein` +- [ ] Benachrichtigung: ein neuer `Meldeanlass` `LAUF_FREIGABE_OFFEN` an die Rollen mit dem Recht — + sonst wartet ein Lauf, von dem niemand weiß +- [ ] Oberfläche: Hinweis am Lauf („wartet auf Freigabe durch einen Zweiten“), Knöpfe für Freigeben + und Zurückweisen, Notizfeld; die Übersicht bekommt einen Filter „wartet auf meine Freigabe“ +- [ ] Editor: Häkchen „dieser Lauf braucht eine Freigabe“ an der Version +- [ ] Druckansicht und PDF nennen Freigeber und Zeitpunkt +- [ ] Anleitung: Kapitel 02 (Läufe abarbeiten) und 04 (Vorlagen), mit dem Hinweis, dass ein + Einzelkämpfer sich nicht selbst freigeben kann + +## Akzeptanzkriterien + +- [ ] Test: ein Lauf mit Freigabepflicht lässt sich nicht abschließen, solange keine Freigabe steht +- [ ] Test: der Bearbeiter kann **nicht** freigeben — auch nicht als `ADMIN` +- [ ] Test: die Datenbank weist es ebenfalls ab (CHECK), nicht nur der Dienst +- [ ] Test: eine Rolle ohne `freigeben` wird abgewiesen, über jede Server Action +- [ ] Test: Zurückweisen verlangt eine Notiz und setzt den Lauf auf `IN_ARBEIT` +- [ ] Test: die Meldung geht an die Rollen mit dem Recht und nicht an alle +- [ ] Test: ein Lauf **ohne** Freigabepflicht verhält sich unverändert — bestehende Läufe und + Vorlagen merken nichts +- [ ] Am Handy kein horizontales Scrollen diff --git a/docs/issues/041-pdf-uebergabeprotokoll.md b/docs/issues/041-pdf-uebergabeprotokoll.md new file mode 100644 index 0000000..adf0eff --- /dev/null +++ b/docs/issues/041-pdf-uebergabeprotokoll.md @@ -0,0 +1,68 @@ +# #041 M25 – PDF-Übergabeprotokoll mit Unterschrift + +**Status:** offen +**Im Tracker:** – +**Meilenstein:** v0.3.0 +**Labels:** phase-3, backend, frontend +**Geschätzt:** 8 h **Tatsächlich:** – + +## Ziel + +Wer ein Notebook übergibt, braucht ein Blatt, das der Empfänger unterschreibt — und das danach +auffindbar ist. Heute gibt es die Druckansicht eines Laufs (`/drucken/lauf/[id]`, #015): sie ist +hell, vollständig und als Vorstufe genau dafür gebaut. Was fehlt, ist die Datei. + +Ein Browser-Ausdruck genügt nicht: er hängt am Drucker-Dialog des Betrachters, trägt dessen +Kopfzeilen und lässt sich nicht an ein Ticket anhängen. + +## Zu entscheiden + +- [ ] **Wie entsteht das PDF?** Empfehlung: **Chromium aus `@playwright/test`**, wie + `scripts/anleitung-pdf.ts` es für die Anleitung tut. Keine neue Abhängigkeit, keine zweite + Auslegung des Layouts — gedruckt wird dieselbe Seite, die die Druckansicht zeigt. + **Grenze, die genannt gehört:** damit braucht der Server einen Chromium. Im Compose-Stand ist + das eine Ergänzung am Image, nativ ein Paket. Die Alternative (`pdf-lib` oder ein + Dienst) wäre ein zweites Layout, das auseinanderläuft — der Preis ist höher. +- [ ] **Unterschrift auf Papier oder auf dem Gerät?** Empfehlung: **beides, in dieser Reihenfolge.** + Zuerst ein Unterschriftsfeld im PDF (eine Linie, Name, Datum) — das löst den Fall heute und + kostet fast nichts. Eine Unterschrift mit dem Finger auf dem Handy ist ein eigener Schritt-Typ + und gehört zu [#042](042-weitere-schritt-typen.md); sie landet dann als Bild im PDF. + Eine digitale **Signatur** im kryptografischen Sinn ist ausdrücklich **nicht** gemeint und + wäre eine eigene Phase. +- [ ] **Wird das PDF gespeichert oder bei jedem Abruf erzeugt?** Empfehlung: **bei jedem Abruf + erzeugt**, nicht gespeichert. Ein gespeichertes PDF ist eine zweite Wahrheit neben dem Lauf; + wird eine Notiz später berichtigt, stimmt es nicht mehr. Für den Fall „wir brauchen das Blatt + von damals“ gibt es das Archiv, das den Lauf unveränderlich hält. + **`{{generiertes_passwort}}` erscheint auch hier nicht** — die Druckansicht zeigt + `[Passwort – nicht gespeichert]`, und das PDF übernimmt das. + +## Aufgaben + +- [ ] `GET /api/laeufe/[id]/protokoll.pdf` — Route Handler, prüft sein Recht **selbst** + (`tests/zugang.test.ts` erzwingt das seit #038) +- [ ] Erzeugung über Chromium, gegen die eigene Druckansicht; eine Seitenvorlage mit Kopf (Kunde, + Lauf, Vorlage, Version), Inhalt und Fuß (Bearbeiter, Freigeber aus + [#040](040-vier-augen-freigabe.md), Unterschriftsfeld) +- [ ] Ein **Chromium-Vorrat** im Prozess: ihn je Anfrage zu starten kostet Sekunden. Eine Instanz, + die nach Leerlauf schließt — und eine Obergrenze gleichzeitiger Erzeugungen, wie + `SSE_MAX_STROEME` in #033 +- [ ] Knopf „Protokoll als PDF“ an Lauf und Archiv +- [ ] `Dockerfile` und `deploy/install.sh` um Chromium ergänzen, **mit gemessener Bildgröße** im + Changelog — eine Ergänzung am Image, die niemand bemerkt, ist eine böse Überraschung beim + nächsten Rollout +- [ ] `docs/betrieb/` sagt, was die Erzeugung braucht und was passiert, wenn Chromium fehlt: + **eine Meldung, kein stilles leeres PDF** +- [ ] Anleitung Kapitel 02 und 10 (Archiv) + +## Akzeptanzkriterien + +- [ ] Test: das PDF enthält Titel, Vorlage mit Version, alle geltenden Schritte mit Zustand, Notizen + und Kommentare — geprüft am extrahierten Text, nicht am Vorhandensein der Datei +- [ ] Test: es enthält **kein** erzeugtes Passwort, über alle Schritt-Typen +- [ ] Test: ein Lauf eines fremden Kunden ist über die Adresse nicht abrufbar (wie #032 nach #036) +- [ ] Test: ohne Recht `401`, mit falschem Kunden `404` +- [ ] Test: fehlt Chromium, kommt eine Meldung mit Hinweis auf die Betriebsdoku — kein leeres PDF +- [ ] Test: zwei gleichzeitige Erzeugungen laufen, die dritte wartet oder wird abgewiesen (die + Obergrenze gilt) +- [ ] Ein Blatt ist ausgedruckt und gegengesehen worden — das ist eine Abnahme von Hand und steht + als solche im Devlog diff --git a/docs/issues/042-weitere-schritt-typen.md b/docs/issues/042-weitere-schritt-typen.md new file mode 100644 index 0000000..9b6fe11 --- /dev/null +++ b/docs/issues/042-weitere-schritt-typen.md @@ -0,0 +1,62 @@ +# #042 M26 – Weitere Schritt-Typen + +**Status:** offen +**Im Tracker:** – +**Meilenstein:** v0.3.0 +**Labels:** phase-3, backend, frontend +**Geschätzt:** 6 h **Tatsächlich:** – + +## Ziel + +`SchrittTyp` hat heute sechs Werte: `HAKEN TEXT ZAHL AUSWAHL DATUM BEFEHL`. Das Enum und das +Json-Feld `konfig` sind seit #006 genau dafür gebaut, dass neue Typen ohne Migration der +bestehenden dazukommen. + +Die Frage ist nicht, **wie** ein Typ dazukommt, sondern **welche** — und das Issue nennt sie +absichtlich noch nicht als beschlossen: ein Typ, der im Editor steht und in keiner Vorlage +auftaucht, ist ungenutzter und damit ungeprüfter Code. + +## Zu entscheiden + +- [ ] **Welche Typen?** Vorschlag, nach Nutzen geordnet, jeder mit dem Fall, der ihn verlangt: + + | Typ | Der Fall | Ohne ihn | + |---|---|---| + | `DATEI` | „Foto des Typenschilds“ als **Pflicht** | ein Anhang ist seit #032 möglich, aber nie verlangt — ein Lauf schließt ohne ihn ab | + | `UNTERSCHRIFT` | Übergabe mit dem Finger auf dem Handy | das Blatt muss gedruckt und wieder eingescannt werden ([#041](041-pdf-uebergabeprotokoll.md)) | + | `MEHRFACHAUSWAHL` | „welche Lizenzen zugewiesen“ | heute ein Textfeld, also nicht auswertbar | + | `URL` | „Link zum Ticket“ | ein Textfeld ohne Prüfung und ohne Klick | + | `UEBERSCHRIFT` | eine Zwischenüberschrift ohne Zustand | ein `HAKEN`, den jemand abhaken muss, damit der Lauf fertig wird | + + **Empfehlung: `DATEI` und `UNTERSCHRIFT`**, und die drei anderen nur, wenn eine Seed-Vorlage + sie wirklich benutzt. `UEBERSCHRIFT` ist der zweifelhafteste: ein Schritt ohne Zustand bricht + die Annahme „jeder Schritt hat ein `Schrittergebnis`“, und die steckt an vielen Stellen. +- [ ] **Bleibt `Schrittergebnis.wert` ein Json?** Ja — es ist seit #006 eines, genau dafür. Eine + Mehrfachauswahl ist ein Array, eine Unterschrift ein Verweis auf einen `Anhang`. +- [ ] **Wie verträgt sich `DATEI` mit dem Archiv-Schreibschutz?** Ein Pflicht-Anhang muss vor dem + Abschluss da sein; danach kommt keiner mehr weg (#032). Das ist bereits so und braucht nur + einen Test. + +## Aufgaben + +- [ ] Je gewählten Typ: Enum-Wert, Zod-Schema für `konfig` und `wert`, Darstellung im Editor, + Darstellung in der Abarbeitung, Darstellung im Druck und im PDF +- [ ] Die Abschlussregel kennt den neuen Typ: was zählt als „erledigt“? Bei `DATEI` ein Anhang, bei + `UNTERSCHRIFT` ein Bild, bei `MEHRFACHAUSWAHL` mindestens eine Wahl +- [ ] `UNTERSCHRIFT`: Zeichenfläche am Handy (Pointer-Ereignisse, nicht Touch-spezifisch), Ergebnis + als PNG über den Anhänge-Weg aus #032 — **keine neue Ablage** +- [ ] Export/Import und die Vorlagen-Austauschform um die neuen Typen erweitern, nachsichtig: eine + Datei mit einem unbekannten Typ wird abgewiesen **mit Nennung des Typs**, nicht stillschweigend + verändert +- [ ] Mindestens eine Seed-Vorlage benutzt jeden neuen Typ — sonst ist er ungeprüfter Code +- [ ] Anleitung Kapitel 04, mit dem Satz, wann ein Typ das richtige Mittel ist + +## Akzeptanzkriterien + +- [ ] Test je Typ: anlegen, abarbeiten, abschließen, drucken +- [ ] Test: ein `DATEI`-Pflichtschritt ohne Anhang hält den Abschluss auf +- [ ] Test: eine Unterschrift landet als `Anhang` und nicht als Json-Blob in `wert` +- [ ] Test: ein Lauf einer **älteren** Version verhält sich unverändert — die neuen Typen tauchen + dort nicht auf, und keine Migration hat Daten angefasst +- [ ] Test: eine Austauschdatei mit unbekanntem Typ wird abgewiesen und nennt den Typ +- [ ] Am Handy bedienbar, bei 390 px geprüft — besonders die Zeichenfläche diff --git a/docs/issues/README.md b/docs/issues/README.md index ca5219e..33eac95 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -43,4 +43,8 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac | #036 | [Reviewbefunde aus der Durchsicht von Phase 2](036-reviewbefunde-phase-2.md) | erledigt | v0.2.0 | 4 h | 3 h 40 min | | #037 | [Zwei Nachzügler aus der Durchsicht von Phase 2](037-anhaenge-lesemodell.md) | erledigt | v0.2.0 | 2 h | 1 h 50 min | | #038 | [Die E2E-Abläufe laufen in keinem Tor und waren monatelang rot](038-e2e-im-tor.md) | erledigt | v0.2.0 | 3 h | 1 h 35 min | -| | **Summe** | | | **238 h** | **73 h 55 min** | +| #039 | [M23 – Bedingte Schritte und Abschnitte](039-bedingte-schritte.md) | offen | v0.3.0 | 12 h | – | +| #040 | [M24 – Vier-Augen-Freigabe](040-vier-augen-freigabe.md) | offen | v0.3.0 | 8 h | – | +| #041 | [M25 – PDF-Übergabeprotokoll mit Unterschrift](041-pdf-uebergabeprotokoll.md) | offen | v0.3.0 | 8 h | – | +| #042 | [M26 – Weitere Schritt-Typen](042-weitere-schritt-typen.md) | offen | v0.3.0 | 6 h | – | +| | **Summe** | | | **272 h** | **73 h 55 min** | diff --git a/scripts/pruefe-doku.mjs b/scripts/pruefe-doku.mjs index 50b1ed5..b7f2fc4 100755 --- a/scripts/pruefe-doku.mjs +++ b/scripts/pruefe-doku.mjs @@ -141,7 +141,7 @@ function feld(text, name) { // README fehlte `format:pruefen`, und beide nannten den LDAP-Container nicht, ohne // den die Suite 22 Tests still ueberspringt. Beides fiel erst bei der Abnahme zu // #019 auf -- ein gruener Lauf sieht genauso aus. Geprueft wird deshalb der -// Befehlsblock selbst, nicht seine Umgebung (#040). +// Befehlsblock selbst, nicht seine Umgebung (#026). { /** Der erste eingezaeunte Block nach einer Ueberschrift. */ const blockNach = (text, ueberschrift) => { @@ -197,6 +197,60 @@ function feld(text, name) { } } +// ---- 7. Jeder (#NNN)-Verweis zeigt auf eine Issue-Datei -------------------- +{ + /* + * Verweise wie `(#032)` stehen in Kommentaren, im Changelog und in CLAUDE.md + * und sind die einzige Bruecke zwischen einer Codestelle und ihrer + * Begruendung. Zeigt einer ins Leere, ist die Begruendung verloren — und + * schlimmer: zeigt er auf die **falsche** Datei, fuehrt er jemanden in die + * Irre, der ihm vertraut. + * + * Genau das war passiert: vier Codestellen und sechs Changelog-Zeilen nannten + * dreistellig die **Tracker**-Nummern 39 und 40 — gemeint waren die + * Issue-Dateien 025 und 026. Solange es keine Dateien mit diesen Nummern gab, + * fiel es niemandem auf; mit Phase 3 waere es eine falsche Spur geworden. + * Verbindlich ist die **Dateinummer**; die Tracker-Nummer steht in der + * Issue-Datei unter „Im Tracker“. + * + * Diese Datei nennt deshalb selbst keine Nummer in der dreistelligen Form — + * sonst meldete die Pruefung ihren eigenen Kommentar. + */ + const verz = "docs/issues"; + const nummern = new Set( + readdirSync(join(wurzel, verz)) + .filter((n) => /^\d{3}-.+\.md$/.test(n)) + .map((n) => n.slice(0, 3)), + ); + + const sammeln = (ordner) => + readdirSync(ordner, { withFileTypes: true }).flatMap((eintrag) => { + if (["node_modules", ".git", ".next", "generated", "test-results"].includes(eintrag.name)) { + return []; + } + const pfad = join(ordner, eintrag.name); + if (eintrag.isDirectory()) return sammeln(pfad); + return /\.(ts|tsx|mjs|md|sql)$/.test(eintrag.name) ? [pfad] : []; + }); + + for (const datei of sammeln(wurzel)) { + const kurz = datei.replace(`${wurzel}/`, ""); + // Der Devlog zitiert Tracker-Nummern aus Pull Requests; dort gilt die Regel nicht. + if (kurz === "docs/devlog.md") continue; + const text = readFileSync(datei, "utf8"); + const gemeldet = new Set(); + for (const treffer of text.matchAll(/#(\d{3})\b/g)) { + const nummer = treffer[1]; + if (nummern.has(nummer) || gemeldet.has(nummer)) continue; + gemeldet.add(nummer); + melde( + `${kurz} verweist auf #${nummer}, aber ${verz}/${nummer}-*.md gibt es nicht. ` + + `Gemeint ist vielleicht eine Tracker-Nummer — verbindlich ist die Dateinummer.`, + ); + } + } +} + // ---- Ergebnis -------------------------------------------------------------- if (befunde.length > 0) { console.error(`\nDokumentation und Code laufen auseinander (${befunde.length}):\n`); diff --git a/src/app/api/gesundheit/route.ts b/src/app/api/gesundheit/route.ts index 4a5b597..ecb0562 100644 --- a/src/app/api/gesundheit/route.ts +++ b/src/app/api/gesundheit/route.ts @@ -10,7 +10,7 @@ import { prisma } from "@/lib/db"; * Datenbanknamen. Nach aussen genuegt, **ob** es geht; warum nicht, steht im * Protokoll des Prozesses (`docker compose logs web`, * `journalctl -u laufzettel`). Dasselbe Muster wie in - * `src/server/actions/verzeichnis.ts` (#039). + * `src/server/actions/verzeichnis.ts` (#025). */ export async function GET(): Promise { try { diff --git a/src/server/graph/zugang.ts b/src/server/graph/zugang.ts index e94f6d0..f77252b 100644 --- a/src/server/graph/zugang.ts +++ b/src/server/graph/zugang.ts @@ -149,7 +149,7 @@ export async function tokenHolen(zugang: GraphZugang, jetzt = Date.now()): Promi /* * Die ausführliche Meldung ins Protokoll des Prozesses, eine kurze nach * draußen. Dieselbe Trennung wie beim LDAP-Bind-Fehler (#016) und beim - * Healthcheck (#039): eine Meldung, die Mandant und Anwendungs-ID nennt, + * Healthcheck (#025): eine Meldung, die Mandant und Anwendungs-ID nennt, * gehört nicht in eine Oberfläche. */ console.error( diff --git a/src/server/scim/handler.ts b/src/server/scim/handler.ts index 5ed97c8..d61e1c2 100644 --- a/src/server/scim/handler.ts +++ b/src/server/scim/handler.ts @@ -46,7 +46,7 @@ export function alsScimAntwort(fehler: unknown): Response { /* * Alles andere ist ein Fehler bei uns, nicht beim Anbieter. Die Meldung geht * ins Protokoll des Prozesses und **nicht** in die Antwort: eine - * Datenbankmeldung nennt Host, Port und Benutzernamen (#039). + * Datenbankmeldung nennt Host, Port und Benutzernamen (#025). */ console.error( "SCIM-Anfrage fehlgeschlagen:", diff --git a/tests/gesundheit.test.ts b/tests/gesundheit.test.ts index 5272087..51c24df 100644 --- a/tests/gesundheit.test.ts +++ b/tests/gesundheit.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; /** - * Der Healthcheck (#039). + * Der Healthcheck (#025). * * Dieser Pfad ist der einzige, der ohne Anmeldung **und** mit Datenbankzugriff * antwortet. Geprueft wird deshalb nicht, dass er funktioniert — das tun der