Versionierung und Aenderungshistorie je Szenario
Deploy App / deploy (push) Successful in 1m48s

Version A.B: B automatisch je Bearbeitungssitzung (10-Minuten-Fenster,
unveraenderte Staende erzeugen keine), A manuell mit Pflichtkommentar.

Eine Version haelt den vollstaendigen Zustand als PlanInput-JSON -- dadurch
ist die Versionsauswahl in allen vier Analysewerkzeugen fast kostenlos,
bei Monte-Carlo je Szenario einzeln.

Wiederherstellen erhaelt die IDs (sonst verlieren Kind-Szenarien ihre
Diff-Basis) und legt den Stand selbst als neue Version an. Wo ein Bezug
trotzdem bricht, warnt der Dialog vorher namentlich.

Statischer Waechter-Test: jeder schreibende Endpunkt loest eine Version aus.
Migration gegen echtes Postgres verifiziert.

Spezifikation 0.19 (3.8 und 9.28 neu), 25 Tests (139 -> 164).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 22:09:10 +02:00
parent d04e07fdfb
commit d023534a03
28 changed files with 2021 additions and 13 deletions
@@ -0,0 +1,44 @@
-- Versionierung je Szenario (SPEZIFIKATION 3.8, 4.16).
--
-- Eine Version haelt den VOLLSTAENDIGEN Zustand des Szenarios als JSON (Form: PlanInput),
-- nicht eine Differenz. Grund: Alle Analysewerkzeuge nehmen ohnehin PlanInput entgegen --
-- damit lassen sich alte Staende ohne Umbau rechnen, anzeigen und wiederherstellen.
-- Laufende Hauptversion des Szenarios. Nebenversionen zaehlen in ScenarioVersion.
ALTER TABLE "Scenario" ADD COLUMN "currentMajor" INTEGER NOT NULL DEFAULT 1;
CREATE TABLE "ScenarioVersion" (
"id" TEXT NOT NULL,
"scenarioId" TEXT NOT NULL,
-- Version A.B: major wird manuell hochgezogen, minor je Bearbeitungssitzung.
"major" INTEGER NOT NULL,
"minor" INTEGER NOT NULL,
-- Pflicht bei Hauptversionen, sonst leer. Wiederherstellungen tragen hier ihre Herkunft.
"comment" TEXT,
"isMajor" BOOLEAN NOT NULL DEFAULT false,
-- Wer die Version erzeugt hat. Heute immer der Eigentuemer; vorbereitet auf spaetere
-- Freigabe an einen Finanzberater.
"createdById" TEXT NOT NULL,
-- Vollstaendiger Zustand (PlanInput als JSON).
"snapshot" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Wird bei Zusammenfassung innerhalb einer Bearbeitungssitzung mitgezogen.
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ScenarioVersion_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "ScenarioVersion_scenarioId_major_minor_key"
ON "ScenarioVersion"("scenarioId", "major", "minor");
-- Die Historie wird immer neuestzuerst gelesen.
CREATE INDEX "ScenarioVersion_scenarioId_createdAt_idx"
ON "ScenarioVersion"("scenarioId", "createdAt");
ALTER TABLE "ScenarioVersion" ADD CONSTRAINT "ScenarioVersion_scenarioId_fkey"
FOREIGN KEY ("scenarioId") REFERENCES "Scenario"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Beim Loeschen eines Benutzers verschwinden dessen Plaene ohnehin per Cascade; der
-- Verweis bleibt bewusst RESTRICT-frei, damit die Historie nie ein Loeschen blockiert.
ALTER TABLE "ScenarioVersion" ADD CONSTRAINT "ScenarioVersion_createdById_fkey"
FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+33 -1
View File
@@ -32,7 +32,8 @@ model User {
passwordHash String
createdAt DateTime @default(now())
plans Plan[]
plans Plan[]
versions ScenarioVersion[]
}
enum HouseholdType {
@@ -114,9 +115,40 @@ model Scenario {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Laufende Hauptversion. Die Nebenversionen zaehlen in ScenarioVersion.
currentMajor Int @default(1)
persons Person[]
phases Phase[]
elements FinancialElement[]
versions ScenarioVersion[]
}
// Ein festgehaltener Stand eines Szenarios (Version A.B). Haelt den VOLLSTAENDIGEN Zustand
// als JSON in der Form PlanInput -- nicht eine Differenz. Dadurch laesst sich jeder alte
// Stand ohne Umbau rechnen (computePlan), anzeigen und wiederherstellen.
model ScenarioVersion {
id String @id @default(cuid())
scenarioId String
scenario Scenario @relation(fields: [scenarioId], references: [id], onDelete: Cascade)
major Int
minor Int
// Pflicht bei Hauptversionen; Wiederherstellungen tragen hier ihre Herkunft.
comment String?
isMajor Boolean @default(false)
createdById String
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
snapshot Json
createdAt DateTime @default(now())
// Wird bei Zusammenfassung innerhalb einer Bearbeitungssitzung mitgezogen.
updatedAt DateTime @updatedAt
@@unique([scenarioId, major, minor])
@@index([scenarioId, createdAt])
}
// Ein Lebensabschnitt innerhalb eines Plans. Der Phasentyp (Erwerb/Pension/Mischung)