feat(laeufe): Uebergabeprotokoll als PDF (#041)
Neben "Drucken" steht am Lauf "Als PDF", im Archiv an jedem Treffer "Protokoll als PDF" — dasselbe Blatt als fertige Datei, zum Verschicken oder Abheften. Unten darauf jetzt zwei Unterschriftsfelder mit Zeile fuer Ort, Datum und Namen; wo eine Vier-Augen-Freigabe erteilt wurde, steht sie mit im Kopf. Erzeugt wird es, indem der Server seine eigene Druckansicht ueber 127.0.0.1 laedt und druckt — mit dem Sitzungscookie des Anfragenden. Damit gibt es genau eine Vorlage fuer Papier und Datei, und fuer das PDF gilt dieselbe Rechte- und Mandantenpruefung wie fuer die Seite. Ein zweites Layout waere billiger und wuerde frueher oder spaeter abweichen, ohne dass es jemandem auffaellt. Ein eigener Browser-Kontext je PDF, kein geteilter: er traegt die fremde Sitzung, und ein geteilter gaebe sie an das naechste PDF weiter. Gespeichert wird nichts — ein abgelegtes PDF waere eine zweite Wahrheit neben dem Lauf. Ein erzeugtes Passwort steht auch hier nie darauf, geprueft am extrahierten Text des fertigen PDF und nicht an den Bytes: der Inhalt ist komprimiert, dort nichts zu finden bewiese nichts. Chromium bleibt optional, und die gemessene Zahl ist der Grund: das Laufzeitbild waechst von 370 MB auf 1,52 GB, Alpines chromium kostet allein 1,13 GB. Er kommt nur mit `--build-arg MIT_PDF=1` beziehungsweise `deploy/install.sh --mit-pdf` mit. Fehlt er, erscheint der Knopf gar nicht erst — ein Knopf, der zuverlaessig in eine Fehlermeldung fuehrt, ist schlimmer als keiner. Ein direkter Aufruf bekommt 503 mit einer Meldung, die sagt, was fehlt und wo es steht; ein leeres PDF gibt es in keinem Fall. Meine Schaetzung lag um das Dreieinhalbfache daneben: "+328 MB" war das .apk-Paket, nicht das Bild, und stand schon im Dockerfile-Kommentar, bevor ich gebaut hatte. Jetzt steht die gemessene Zahl an drei Stellen. 1545 Vitest-Tests (vorher 1526): 15 in tests/pdf.test.ts, die den Handler echt fahren, 3 in tests/pdf-grenze.test.ts fuer die Obergrenze mit gestelltem Browser, einer in tests/archiv.test.ts. Dazu drei E2E-Ablaeufe; volle Suite gruen mit 118 bestanden. Ein Blatt wurde wirklich erzeugt und angesehen. Sechs Gegenproben, alle angeschlagen — darunter ein echtes Passwort auf dem Blatt und die Freigabe des Zaehlers aus dem finally genommen, der Fall, in dem ein Fehlschlag die PDF-Erzeugung bis zum Neustart leise stilllegt. Closes #41 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d30bb6d235
commit
f3950847f3
@@ -321,6 +321,29 @@ abheftet, darf keines stehen.
|
||||
Von der Person stehen nur Name, UPN und Abteilung auf dem Beleg. Ein Ausdruck liegt herum, wird
|
||||
kopiert und weitergegeben; was darauf steht, ist aus der Hand gegeben.
|
||||
|
||||
Unten auf dem Blatt stehen zwei Unterschriftsfelder — **Übergeben von** und **Übernommen von**, je
|
||||
mit einer Zeile für Ort, Datum und Namen in Druckbuchstaben. Damit ist der Beleg gleich das
|
||||
Übergabeprotokoll: Gerät wechselt den Besitzer, beide unterschreiben, fertig.
|
||||
|
||||
### Als PDF
|
||||
|
||||
Neben **Drucken** steht **Als PDF**. Der Knopf lädt dasselbe Blatt als fertige PDF-Datei
|
||||
herunter — ohne den Umweg über den Druckdialog. Praktisch für den, der ein Protokoll verschicken
|
||||
oder ablegen will statt es auf Papier zu bringen.
|
||||
|
||||
Im **Archiv** steht an jedem Treffer derselbe Knopf, dort als **Protokoll als PDF**. Wer ein altes
|
||||
Protokoll für die Ablage oder für einen Kunden braucht, holt es damit direkt aus der Trefferliste,
|
||||
ohne den Lauf erst zu öffnen.
|
||||
|
||||
> **Fehlt der Knopf, ist er nicht kaputt.** Die PDF-Erzeugung braucht ein zusätzliches Programm auf
|
||||
> dem Server, und das ist nicht überall installiert — es kostet mehr Platz als die ganze Anwendung.
|
||||
> Dann bleibt **Drucken** der Weg: im Browser „in eine Datei drucken“ liefert dasselbe Blatt.
|
||||
> Wer es dauerhaft haben will, fragt den Administrator; die Einzelheiten stehen in
|
||||
> [`docs/betrieb/pdf.md`](../betrieb/pdf.md).
|
||||
|
||||
Auf dem PDF steht genau dasselbe wie auf dem gedruckten Blatt — es **ist** dieselbe Ansicht. Auch
|
||||
hier also kein erzeugtes Passwort.
|
||||
|
||||
## Im Archiv suchen
|
||||
|
||||

|
||||
|
||||
@@ -62,6 +62,9 @@ Sitzungen ungültig *und* die verschlüsselt gespeicherten LDAP-Bind-Passwörter
|
||||
Signatur nachrechnen, und wo man nachsieht, wenn keine Mail mehr ankommt
|
||||
- **[SCIM-Endpunkt](scim.md)** — die einzige Stelle, an der ein fremder Dienst von außen schreibend
|
||||
zugreift: das Token, was nicht unterstützt wird, und was der Reverse Proxy durchlassen muss
|
||||
- **[Das Übergabeprotokoll als PDF](pdf.md)** — eine Zusatzausstattung, die Chromium verlangt und
|
||||
das Bild von 370 MB auf 1,52 GB bringt: wann sie sich lohnt, wie sie dazukommt, und was passiert,
|
||||
wenn man sie weglässt
|
||||
|
||||
## Wo sonst noch etwas steht
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ Jede steht mit Kommentar in [`../../.env.example`](../../.env.example). **Pflich
|
||||
| `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD` | Der Administrator, den der Seed anlegt. Das Passwort wird nur als argon2-Hash gespeichert. Fehlt eines, überspringt der Seed ihn — und dann kommst du nicht hinein. |
|
||||
| `SEED_MODE` | `INTERN` (Standard) oder `DIENSTLEISTUNG`, `SERVICE` als Alias. Betrifft nur den **ersten** Seed-Lauf; danach steht der Modus in der Datenbank und wird in der Oberfläche umgeschaltet. |
|
||||
| `SEED_ORG_NAME`, `SEED_ORG_KUERZEL` | Name und Kürzel der eigenen Organisation. Das Kürzel (2 bis 6 Großbuchstaben oder Ziffern) steht auf Etiketten und in der Hostname-Regel. |
|
||||
| `CHROMIUM_PFAD`, `PDF_MAX_GLEICHZEITIG`, `PDF_LEERLAUF_MS` | Nur wenn das Übergabeprotokoll als PDF entstehen soll. Das verlangt Chromium im Bild (`--build-arg MIT_PDF=1`) und bringt es von 370 MB auf 1,52 GB; ohne ihn erscheint der Knopf nicht und die Druckansicht bleibt der Weg. Alles dazu in [`pdf.md`](pdf.md). |
|
||||
| `TEST_DATABASE_URL` | Nur für die Tests. Im Betrieb ohne Bedeutung. |
|
||||
|
||||
## 4. Starten
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# Das Übergabeprotokoll als PDF
|
||||
|
||||
Jeder Lauf lässt sich als PDF herunterladen — ein Blatt mit Kopfdaten, allen Schritten samt
|
||||
Zuständen und Notizen, den Kommentaren und zwei Unterschriftsfeldern. Gedacht ist es für die
|
||||
Übergabe: ein Gerät wechselt den Besitzer, beide unterschreiben, das Blatt geht in die Ablage oder
|
||||
zum Kunden.
|
||||
|
||||
**Das ist eine Zusatzausstattung, und sie kostet.** Ohne sie fehlt nichts Wesentliches: die
|
||||
Druckansicht zeigt dasselbe Blatt, und jeder Browser kann daraus „in eine Datei drucken“. Das PDF
|
||||
vom Server nimmt diesen Schritt nur ab — für den, der zwanzig Protokolle am Stück braucht, oder für
|
||||
den, der sie aus dem Archiv heraus einzeln verschickt.
|
||||
|
||||
## Was es braucht
|
||||
|
||||
**Chromium auf dem Server.** Nichts weiter — keine Schriften aus dem Netz, kein Dienst, kein
|
||||
zusätzlicher Port.
|
||||
|
||||
Der Grund: das PDF entsteht, indem der Server seine **eigene** Druckansicht lädt, so wie ein
|
||||
Browser es täte, und sie druckt. Dadurch gibt es genau eine Vorlage für Papier und PDF. Ein zweites
|
||||
Layout im Code wäre billiger zu betreiben und würde früher oder später von der Druckansicht
|
||||
abweichen, ohne dass es jemandem auffällt — bis ein Kunde zwei verschiedene Blätter nebeneinander
|
||||
legt.
|
||||
|
||||
Chromium ist der Preis dafür, und er ist hoch:
|
||||
|
||||
| Laufzeitbild | Größe |
|
||||
|---|---|
|
||||
| ohne Chromium | **370 MB** |
|
||||
| mit Chromium | **1,52 GB** |
|
||||
|
||||
Gemessen am 2026-10-25. Alpines `chromium` kostet allein 1,13 GB — mehr als das Dreifache der
|
||||
gesamten Anwendung. Deshalb ist er nicht im Standardbild, und deshalb ist diese Seite kein Anhang,
|
||||
sondern eine Entscheidung.
|
||||
|
||||
## Mit Docker Compose
|
||||
|
||||
Chromium kommt über ein Bau-Argument ins Bild:
|
||||
|
||||
```bash
|
||||
docker compose build --build-arg MIT_PDF=1 web
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Damit das beim nächsten `docker compose build` nicht verlorengeht, gehört es in die
|
||||
`docker-compose.yml` beziehungsweise in eine Override-Datei:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
args:
|
||||
MIT_PDF: "1"
|
||||
```
|
||||
|
||||
Mehr ist nicht zu tun: das Paket liegt dann unter `/usr/bin/chromium-browser`, und dort sucht die
|
||||
Anwendung von sich aus.
|
||||
|
||||
## Nativ, im LXC oder auf einem Debian
|
||||
|
||||
`deploy/install.sh` fragt beim ersten Lauf, ob Chromium mit soll, und merkt die Antwort in
|
||||
`/etc/laufzettel/laufzettel.env`. Nachträglich:
|
||||
|
||||
```bash
|
||||
apt-get install -y chromium
|
||||
systemctl restart laufzettel
|
||||
```
|
||||
|
||||
Auf Debian 13 liegt es danach unter `/usr/bin/chromium`. Der Neustart ist nötig, weil die Anwendung
|
||||
den Pfad beim ersten PDF sucht und sich das Ergebnis für die Laufzeit des Prozesses merkt.
|
||||
|
||||
## Wenn Chromium fehlt
|
||||
|
||||
**Dann passiert nichts Stilles.** Der Knopf „Als PDF“ erscheint am Lauf und im Archiv gar nicht
|
||||
erst — die Anwendung sieht bei jedem Seitenaufbau nach, ob ein Chromium da ist. Wer den Endpunkt
|
||||
trotzdem aufruft, etwa aus einem alten Tab oder mit einem Lesezeichen, bekommt `503` und diesen
|
||||
Text:
|
||||
|
||||
> Auf diesem Server ist kein Chromium installiert, und ohne ihn entsteht kein PDF. Die Druckansicht
|
||||
> funktioniert weiterhin: dort im Browser „in eine Datei drucken“. Wie Chromium dazukommt, steht in
|
||||
> docs/betrieb/pdf.md.
|
||||
|
||||
Ein leeres oder halbes PDF gibt es in keinem Fall. Das war die Vorgabe für dieses Verhalten: ein
|
||||
Blatt, das aussieht wie ein Protokoll und keines ist, wäre schlimmer als eine klare Absage.
|
||||
|
||||
## Die drei Einstellungen
|
||||
|
||||
Alle drei sind Vorgaben, die man kennen sollte und selten braucht.
|
||||
|
||||
| Variable | Vorgabe | Wofür |
|
||||
|---|---|---|
|
||||
| `CHROMIUM_PFAD` | leer | Wo Chromium liegt, wenn er woanders liegt |
|
||||
| `PDF_MAX_GLEICHZEITIG` | `2` | Wie viele PDFs gleichzeitig entstehen dürfen |
|
||||
| `PDF_LEERLAUF_MS` | `60000` | Nach wie viel Leerlauf der Browser wieder schließt |
|
||||
|
||||
**`CHROMIUM_PFAD` leer lassen heißt „suche selbst“** — der Reihe nach
|
||||
`/usr/bin/chromium-browser`, `/usr/bin/chromium`, `/usr/bin/google-chrome`. Ein gesetzter Pfad, der
|
||||
ins Leere zeigt, ist dagegen ein **Fehler** und kein Rückfall auf die Suche: wer hier etwas
|
||||
einträgt, hat eine Absicht, und ein Tippfehler soll auffallen und nicht dazu führen, dass
|
||||
stillschweigend ein anderer Browser läuft als der gemeinte.
|
||||
|
||||
**`PDF_MAX_GLEICHZEITIG` schützt den Arbeitsspeicher.** Jedes PDF hält kurz einen eigenen
|
||||
Browser-Kontext offen — eigen, und nicht geteilt, weil er das Sitzungscookie des Anfragenden trägt.
|
||||
Ist die Grenze erreicht, bekommt der nächste die Bitte, es gleich noch einmal zu versuchen, statt
|
||||
einer Maschine, die unter Last steht.
|
||||
|
||||
**`PDF_LEERLAUF_MS`** hält den Chromium nach dem letzten PDF noch eine Minute offen, weil ihn zu
|
||||
starten rund zwei Sekunden kostet und Protokolle meist in Serie entstehen. Danach schließt er: offen
|
||||
belegt er rund 100 MB, und auf einem 2-GB-LXC ist das ein Achtel des Arbeitsspeichers für etwas, das
|
||||
niemand gerade braucht.
|
||||
|
||||
## Was auf dem Blatt steht — und was nicht
|
||||
|
||||
Es steht dasselbe darauf wie in der Druckansicht, denn es **ist** die Druckansicht. Insbesondere:
|
||||
|
||||
- **Kein generiertes Passwort.** Wo ein Befehl `{{generiertes_passwort}}` enthält, steht auf dem
|
||||
Blatt `[Passwort – nicht gespeichert]`. Das Passwort entsteht bei jeder Anzeige neu und wird
|
||||
nirgends gespeichert; ein Ausdruck dagegen liegt herum. Geprüft wird das am extrahierten Text des
|
||||
fertigen PDF, nicht an der Vorlage.
|
||||
- **Keine Anhänge**, nur ihre Anzahl. Ein Foto gehört nicht auf ein Übergabeprotokoll, und ein PDF
|
||||
mit zwanzig Bildern wäre nicht mehr zu verschicken.
|
||||
- **Befehle unmaskiert.** Das Blatt hält fest, was zu tun war; Maskierzeichen wären auf Papier
|
||||
Rauschen. Wer einen Befehl ausführen will, nimmt die Laufseite — dort ist er für die gewählte
|
||||
Shell maskiert.
|
||||
|
||||
## Fehlersuche
|
||||
|
||||
**Der Knopf fehlt, obwohl Chromium installiert ist.** Die Anwendung hat ihn nicht gefunden. Nachsehen,
|
||||
wo er liegt, und `CHROMIUM_PFAD` setzen:
|
||||
|
||||
```bash
|
||||
command -v chromium chromium-browser google-chrome
|
||||
```
|
||||
|
||||
Danach den Dienst neu starten. Im Compose-Stand prüft man im Container selbst, nicht auf dem Host:
|
||||
|
||||
```bash
|
||||
docker compose exec web command -v chromium-browser
|
||||
```
|
||||
|
||||
**`503` mit „Die Druckansicht antwortete mit 307“** (oder 302, oder 401). Das Sitzungscookie kam
|
||||
nicht mit oder gilt nicht mehr — Chromium wurde auf die Anmeldeseite geschickt. Neu anmelden und es
|
||||
noch einmal versuchen. Bleibt es dabei, steht vermutlich ein Reverse Proxy im Weg, der `Cookie`
|
||||
nicht durchlässt.
|
||||
|
||||
**`503` mit „Es entstehen gerade … PDFs gleichzeitig“.** Die Grenze greift. Entweder warten oder
|
||||
`PDF_MAX_GLEICHZEITIG` anheben — und dabei im Blick behalten, dass jeder gleichzeitige Vorgang
|
||||
Arbeitsspeicher kostet.
|
||||
|
||||
**`500` mit „Einzelheiten stehen im Protokoll des Dienstes“.** Hier geht die Meldung absichtlich
|
||||
nicht hinaus, weil eine rohe Ausnahme Pfade und Zugangsdaten nennen kann. Nachsehen:
|
||||
|
||||
```bash
|
||||
docker compose logs -f web | grep PDF # Compose
|
||||
journalctl -u laufzettel -f | grep PDF # nativ
|
||||
```
|
||||
|
||||
**Leere Kästen statt Buchstaben im PDF.** Dem Chromium fehlen Systemschriften. Im Bild ist
|
||||
`font-noto` deshalb mit dabei; nativ gehört es nachinstalliert
|
||||
(`apt-get install -y fonts-noto-core`). Die Schriften der Oberfläche selbst liefert die Anwendung
|
||||
mit, aber für Zeichen, die darin nicht vorkommen, braucht Chromium einen Rückfall.
|
||||
|
||||
## Wo sonst noch etwas steht
|
||||
|
||||
- [`../../.env.example`](../../.env.example) — die drei Variablen mit Kommentar
|
||||
- [`inbetriebnahme.md`](inbetriebnahme.md) — der Compose-Weg im Ganzen
|
||||
- [`proxmox.md`](proxmox.md) — der native Weg und `deploy/install.sh`
|
||||
- [`../anleitung/02-laeufe-abarbeiten.md`](../anleitung/02-laeufe-abarbeiten.md) — wie man das Protokoll als Anwender holt
|
||||
+80
-2
@@ -42,13 +42,14 @@ als Unterpunkte mit Aufgabe und Dauer.
|
||||
| #037 | Zwei Nachzügler aus der Durchsicht von Phase 2 | 1 h 50 min |
|
||||
| #039 | M23 – Bedingte Schritte und Abschnitte | 5 h 20 min |
|
||||
| #040 | M24 – Vier-Augen-Freigabe | 3 h 30 min |
|
||||
| | **Summe** | **82 h 45 min** |
|
||||
| #041 | M25 – PDF-Übergabeprotokoll mit Unterschrift | 4 h 10 min |
|
||||
| | **Summe** | **86 h 55 min** |
|
||||
|
||||
| Meilenstein | Tatsächlich | Status |
|
||||
|---|---|---|
|
||||
| v0.1.0 | 35 h 20 min | veröffentlicht |
|
||||
| v0.2.0 | 38 h 35 min | veröffentlicht |
|
||||
| v0.3.0 | 8 h 50 min | in Arbeit |
|
||||
| v0.3.0 | 13 h 00 min | in Arbeit |
|
||||
|
||||
---
|
||||
|
||||
@@ -1895,3 +1896,80 @@ Wirklichkeit nicht — die Prüflisten stehen in `docs/betrieb/entra-id.md` und
|
||||
### Nächster Schritt
|
||||
|
||||
- **M25 (#041)** PDF-Übergabeprotokoll. Der Freigeber steht jetzt am Lauf und gehört auf das Blatt.
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-25 · Sitzung 27 · 09:30–13:40
|
||||
|
||||
**Issue:** #041 (M25)
|
||||
**Version:** 0.3.0 (unveröffentlicht)
|
||||
|
||||
### Erledigt
|
||||
|
||||
- **Das Übergabeprotokoll als PDF.** `GET /api/laeufe/[id]/protokoll.pdf`, Chromium-Vorrat mit
|
||||
Obergrenze und Leerlauf-Schließung, Knopf am Lauf und an jedem Archiv-Treffer,
|
||||
Unterschriftsfelder und Freigabe-Zeile auf dem Blatt.
|
||||
- **Chromium optional ins Bild**: `--build-arg MIT_PDF=1`, `deploy/install.sh --mit-pdf`,
|
||||
`docs/betrieb/pdf.md`, die drei Variablen in `.env.example` und in der Variablentabelle,
|
||||
Anleitung Kapitel 02.
|
||||
- 1545 Vitest-Tests (vorher 1526): 15 in `tests/pdf.test.ts`, 3 in `tests/pdf-grenze.test.ts`,
|
||||
einer in `tests/archiv.test.ts`. Dazu drei E2E-Abläufe. Volle E2E-Suite grün: 118 bestanden,
|
||||
40 übersprungen.
|
||||
- **Abnahme von Hand:** ein Blatt wirklich erzeugt und angesehen — zwei Seiten A4, 138 kB,
|
||||
Kopfdaten, sieben Schritte mit Zuständen und Notizen, Befehl, Kommentar, beide
|
||||
Unterschriftsfelder mit sichtbarer Linie, Fußzeile mit Druckzeitpunkt und Lauf-Kennung. Umlaute
|
||||
stehen richtig da, also stimmen die Schriften.
|
||||
|
||||
### Entscheidungen
|
||||
|
||||
- **`playwright-core` als echte Abhängigkeit**, nicht `@playwright/test`: das ist ein
|
||||
Entwicklungspaket und hat im Laufzeitbild nichts verloren. Es landet über das Tracing im
|
||||
Standalone-Bündel (12 MB, als relativer Symlink in das mitkopierte `.pnpm`).
|
||||
- **Ein eigener Browser-Kontext je PDF, kein geteilter.** Er trägt das Sitzungscookie des
|
||||
Anfragenden; ein geteilter behielte es und gäbe das nächste PDF mit fremder Sitzung heraus.
|
||||
- **Der Server ruft sich über `127.0.0.1` selbst**, nicht über `APP_URL`. Das wäre ein Weg nach
|
||||
draußen und zurück, mit TLS, Proxy-Regeln und einer zweiten Stelle, an der etwas klemmt.
|
||||
- **Der Knopf erscheint nur, wenn Chromium da ist.** Ein Knopf, der zuverlässig in eine
|
||||
Fehlermeldung führt, ist schlimmer als kein Knopf — die Druckansicht daneben tut dasselbe, und
|
||||
zwar immer. `pdfVerfuegbar()` wirft deshalb nie.
|
||||
- **Ein gesetztes, aber falsches `CHROMIUM_PFAD` ist ein Fehler**, kein Rückfall auf die Suche. Wer
|
||||
dort etwas einträgt, hat eine Absicht; still auszuweichen hieße, dass ein Tippfehler unbemerkt
|
||||
bleibt und ein anderer Browser läuft als der gemeinte.
|
||||
- **Die Unterschriftslinie nimmt `border-text`**, nicht `border-black`. `.druck-hell` setzt
|
||||
`--color-text` auf `#0b0d10` — damit tut das Token genau das, was der rohe Wert sollte, und
|
||||
CLAUDE.md braucht keine siebte Ausnahme.
|
||||
|
||||
### Probleme
|
||||
|
||||
- **Die Bildgröße war um das Dreieinhalbfache danebengeschätzt.** Ich hatte „+328 MB“ im Kopf — das
|
||||
ist das `.apk`-Paket. Gemessen: ohne Chromium **370 MB**, mit **1,52 GB**, Chromium allein also
|
||||
1,13 GB. Die geschätzte Zahl stand schon im Dockerfile-Kommentar, bevor ich gebaut habe. Genau
|
||||
die Art Zusage, die CLAUDE.md anprangert — und diesmal hat die Regel „gemessen, nicht geschätzt“
|
||||
sie noch vor dem Commit gefangen. Nebenbei: `font-noto` sind davon nur 20 MB, die Schriften waren
|
||||
nie das Problem.
|
||||
- **Ein Server aus einer früheren Sitzung lief noch auf Port 3300.** Die Klickprobe zeigte deshalb
|
||||
keinen PDF-Knopf, und ich habe zuerst `pdfVerfuegbar()` verdächtigt. Der Prozess trug alten Code
|
||||
und kein `CHROMIUM_PFAD`. Eine halbe Stunde an einem Fehler, den es nicht gab — vor einer Probe
|
||||
am gebauten Stand gehört geprüft, **welcher** Stand da läuft.
|
||||
- **`tests/zugang.test.ts` blieb grün, als ich die Rechteprüfung des Handlers durch einen festen
|
||||
ADMIN ersetzt habe.** Es sucht nach dem Vorkommen von `rechtErforderlich`, und der Import stand
|
||||
noch da. Das ist die bekannte Grenze eines strukturellen Tests — er erzwingt, dass jemand *etwas*
|
||||
schreibt, nicht dass es wirkt. Genau deshalb fahren `tests/pdf.test.ts` und die
|
||||
Geschwisterdateien den Handler echt, und genau die wurden rot.
|
||||
- **Die Obergrenze hatte zuerst keinen eigenen Prüfer.** Geprüft war `maxPdfs()` und der
|
||||
503-Zweig des Handlers mit gemocktem Fehler — nicht, dass `seiteAlsPdf` die Grenze wirklich
|
||||
durchsetzt. `tests/pdf-grenze.test.ts` fährt die echte Funktion mit gestelltem Browser; die
|
||||
Gegenprobe (Freigabe aus dem `finally` nehmen) deckt den Fall auf, in dem ein Fehlschlag den
|
||||
Zähler nie zurücksetzt und die PDF-Erzeugung bis zum Neustart leise stillsteht.
|
||||
- **Mein Passwort-Suchmuster warf beim Bauen.** `\\-` in einer Vorlagenzeichenkette wird zu `\-`,
|
||||
und mit dem `u`-Schalter ist das eine ungültige Maskierung. Der Bindestrich gehört ans Ende der
|
||||
Zeichenklasse, wo er sich selbst meint.
|
||||
- **Gegenproben, alle angeschlagen:** Mandantenprüfung ausgehebelt (2 Tests rot), Rechteprüfung
|
||||
durch festen ADMIN ersetzt (3 rot), Cookie nicht weitergegeben (1 rot), Obergrenze abgeschaltet
|
||||
(2 rot), Freigabe aus dem `finally` (1 rot) und — die wichtigste — ein echtes Passwort auf das
|
||||
Blatt gelassen: der neue E2E-Ablauf fand es, zusammen mit dem bestehenden Druckansicht-Test.
|
||||
|
||||
### Nächster Schritt
|
||||
|
||||
- **M26 (#042)** weitere Schritt-Typen. Die Unterschrift mit dem Finger gehört dorthin; sie landet
|
||||
dann als Bild im Schritt und damit ohnehin auf diesem Blatt.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# #041 M25 – PDF-Übergabeprotokoll mit Unterschrift
|
||||
|
||||
**Status:** offen
|
||||
**Status:** erledigt
|
||||
**Im Tracker:** –
|
||||
**Meilenstein:** v0.3.0
|
||||
**Labels:** phase-3, backend, frontend
|
||||
**Geschätzt:** 8 h **Tatsächlich:** –
|
||||
**Geschätzt:** 8 h **Tatsächlich:** 4 h 10 min
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -17,19 +17,19 @@ Kopfzeilen und lässt sich nicht an ein Ticket anhängen.
|
||||
|
||||
## Zu entscheiden
|
||||
|
||||
- [ ] **Wie entsteht das PDF?** Empfehlung: **Chromium aus `@playwright/test`**, wie
|
||||
- [x] **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.**
|
||||
- [x] **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
|
||||
- [x] **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.
|
||||
@@ -38,31 +38,51 @@ Kopfzeilen und lässt sich nicht an ein Ticket anhängen.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] `GET /api/laeufe/[id]/protokoll.pdf` — Route Handler, prüft sein Recht **selbst**
|
||||
- [x] `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,
|
||||
- [x] 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,
|
||||
- [x] 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
|
||||
- [x] Knopf „Protokoll als PDF“ an Lauf und Archiv
|
||||
- [x] `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:
|
||||
- [x] `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)
|
||||
- [x] Anleitung Kapitel 02 und 10 (Archiv)
|
||||
|
||||
## Wie entschieden wurde
|
||||
|
||||
**Chromium gegen die eigene Druckansicht**, wie empfohlen — aber über `playwright-core` als echte
|
||||
Abhängigkeit, nicht über `@playwright/test`: das ist ein Entwicklungspaket und hat im
|
||||
Laufzeitbild nichts verloren. Gedruckt wird `/drucken/lauf/[id]` über `127.0.0.1`, mit dem
|
||||
Sitzungscookie des Anfragenden; damit gilt für das PDF dieselbe Rechte- und Mandantenprüfung wie
|
||||
für die Seite, und es gibt genau eine Vorlage für Papier und Datei.
|
||||
|
||||
**Chromium ist optional und nicht im Standardbild.** Die gemessene Zahl war das Argument: das
|
||||
Laufzeitbild wächst von **370 MB auf 1,52 GB**, Alpines `chromium` kostet allein 1,13 GB. Das Bild
|
||||
baut mit `--build-arg MIT_PDF=1`, nativ installiert `deploy/install.sh --mit-pdf`. Ohne Chromium
|
||||
erscheint der Knopf gar nicht erst, und der Endpunkt antwortet `503` mit einer Meldung, die auf
|
||||
`docs/betrieb/pdf.md` verweist — kein stilles leeres PDF.
|
||||
|
||||
Die Schätzung war damals auf „+328 MB“ hochgerechnet. Das war das `.apk`-Paket, nicht das Bild;
|
||||
gemessen ist es das Dreieinhalbfache. Deshalb steht die Zahl jetzt an drei Stellen gemessen da.
|
||||
|
||||
**Nicht gespeichert, bei jedem Abruf erzeugt** — wie empfohlen. Das Unterschriftsfeld ist eine
|
||||
Linie auf Papier; die Unterschrift mit dem Finger bleibt [#042](042-weitere-schritt-typen.md).
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: das PDF enthält Titel, Vorlage mit Version, alle geltenden Schritte mit Zustand, Notizen
|
||||
- [x] 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
|
||||
- [x] Test: es enthält **kein** erzeugtes Passwort, über alle Schritt-Typen
|
||||
- [x] Test: ein Lauf eines fremden Kunden ist über die Adresse nicht abrufbar (wie #032 nach #036)
|
||||
- [x] Test: ohne Recht `401`, mit falschem Kunden `404`
|
||||
- [x] Test: fehlt Chromium, kommt eine Meldung mit Hinweis auf die Betriebsdoku — kein leeres PDF
|
||||
- [x] 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
|
||||
- [x] Ein Blatt ist ausgedruckt und gegengesehen worden — das ist eine Abnahme von Hand und steht
|
||||
als solche im Devlog
|
||||
|
||||
@@ -45,6 +45,6 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac
|
||||
| #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 |
|
||||
| #039 | [M23 – Bedingte Schritte und Abschnitte](039-bedingte-schritte.md) | erledigt | v0.3.0 | 12 h | 5 h 20 min |
|
||||
| #040 | [M24 – Vier-Augen-Freigabe](040-vier-augen-freigabe.md) | erledigt | v0.3.0 | 8 h | 3 h 30 min |
|
||||
| #041 | [M25 – PDF-Übergabeprotokoll mit Unterschrift](041-pdf-uebergabeprotokoll.md) | offen | v0.3.0 | 8 h | – |
|
||||
| #041 | [M25 – PDF-Übergabeprotokoll mit Unterschrift](041-pdf-uebergabeprotokoll.md) | erledigt | v0.3.0 | 8 h | 4 h 10 min |
|
||||
| #042 | [M26 – Weitere Schritt-Typen](042-weitere-schritt-typen.md) | offen | v0.3.0 | 6 h | – |
|
||||
| | **Summe** | | | **272 h** | **82 h 45 min** |
|
||||
| | **Summe** | | | **272 h** | **86 h 55 min** |
|
||||
|
||||
Reference in New Issue
Block a user