PDF-Berichte (Roadmap 11)
Deploy App / deploy (push) Successful in 3m5s

Neuer Unterpunkt "Berichte" je Plan: Liste plus Assistent (Titel, Notiz,
nominal ODER real, Plan-/Ist-Daten, bis zu drei Szenarien, gespeicherte
Analysen). Layout immer gleich, Auswahl bestimmt nur die Bausteine.

Die PDF-Datei wird ALS DATEI abgelegt (BYTEA in Postgres, nicht im
Container-Dateisystem): Ein Bericht muss in drei Jahren byte-identisch
wieder herunterladbar sein -- eine Neuerzeugung koennte das nach
Aenderungen an Plan, Rechenkern oder Layout nicht garantieren.

Kennzahlen je Szenario inkl. offener Entscheide. Deren Zaehlung liegt neu
als reine Funktion in decisions.ts, die Matrix UND Bericht benutzen --
sonst nennen beide verschiedene Zahlen.

Zu jeder Kennzahl ihre Grundlage als Verweis; die vollstaendigen Annahmen
einmal je Szenario. Haftungsausschluss ist verpflichtend (per Test).

Technik: pdfkit in der Node-Runtime statt Headless-Browser;
@react-pdf/renderer bricht mit React 19. Als externes Paket deklariert,
weil pdfkit Font-Metriken ueber Dateipfade laedt.

Spezifikation 0.25 (3.11 neu), 9 Tests (212 -> 221).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 08:30:18 +02:00
parent 489390a4a3
commit d9ef980edf
17 changed files with 1988 additions and 47 deletions
@@ -0,0 +1,38 @@
-- PDF-Berichte (Roadmap Nr. 11, SPEZIFIKATION 3.11).
--
-- Die fertige PDF-Datei wird ALS BYTES abgelegt, nicht nur ihre Definition. Grund ist der
-- Audit-Trail: Ein Bericht, den man heute erzeugt und verschickt, muss in drei Jahren
-- byte-identisch wieder herunterladbar sein -- auch wenn sich Plan, Rechenkern oder Layout
-- zwischenzeitlich geaendert haben. Eine Neuerzeugung koennte das nicht garantieren.
--
-- Der App-Container wird bei jedem Deploy neu gebaut; nur das DB-Volume ueberlebt. Deshalb
-- Postgres und nicht das Dateisystem.
CREATE TABLE "Report" (
"id" TEXT NOT NULL,
"planId" TEXT NOT NULL,
"title" TEXT NOT NULL,
-- Gewaehlte Parameter (Metrik, Quelle, Szenarien, Analysen) -- fuer die Liste und
-- damit nachvollziehbar bleibt, wie der Bericht zustande kam.
"config" JSONB NOT NULL DEFAULT '{}',
-- Das eingefrorene Berichtsmodell (alle Zahlen). Erlaubt eine Zusammenfassung in der
-- Liste, ohne das PDF zu oeffnen.
"model" JSONB NOT NULL DEFAULT '{}',
-- Die Datei selbst.
"pdf" BYTEA NOT NULL,
"pdfBytes" INTEGER NOT NULL DEFAULT 0,
"createdById" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Report_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "Report_planId_createdAt_idx" ON "Report"("planId", "createdAt");
ALTER TABLE "Report" ADD CONSTRAINT "Report_planId_fkey"
FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Report" ADD CONSTRAINT "Report_createdById_fkey"
FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;