Effektive Werte: Plan-Ist-Vergleich (Roadmap 5)
Deploy App / deploy (push) Successful in 1m57s

Neuer Knopf auf Plan-Ebene: Liste plus Wizard in zwei Schritten. Ein
Ist-Satz haengt am PLAN, nicht am Szenario -- die Zuordnung laeuft ueber
die Herkunfts-Kette sourceElementId.

computePlan nimmt neu { actuals }: Die Werte schnappen in jedem erfassten
Jahr auf die Realitaet und laufen von dort planmaessig weiter. Luecken
fallen auf die Plandaten zurueck. Ohne die Option unveraendert -- die 43
Golden Tests laufen durch.

Der Sprung ist keine Rendite: eigene Brueckenposition actualsCorrection
in Vermoegens- und Cash-Bruecke, sonst ginge die Zerlegung nicht auf.

Matrix: Umschalter Plan/Effektiv, im Ist-Modus mit farbiger Abweichung
statt acht Zahlen je Zelle. Zeitachse: Marker je Jahr, juengster farbig.

Vier Analysewerkzeuge mit einheitlicher Leiste (nominal/real als
Einfachauswahl, Plan/Effektiv). MC: Zielbetrag dreht mit, Startjahr
abgeleitet statt eingebbar.

Neue Tabelle ActualsSet (gegen echtes Postgres verifiziert), Module
actuals.ts und dataview.ts. Spezifikation 0.21, 27 Tests (181 -> 208).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 20:35:07 +02:00
parent 4343d50aa4
commit e117e5a4e5
25 changed files with 1991 additions and 74 deletions
@@ -0,0 +1,34 @@
-- Effektive (Ist-)Werte, Roadmap Nr. 5 (SPEZIFIKATION 3.9).
--
-- Ein Ist-Satz gehoert zum PLAN, nicht zum Szenario: Die Realitaet ist dieselbe, egal gegen
-- welches Szenario man sie haelt. Die Zuordnung auf die szenario-eigenen Element-IDs laeuft
-- ueber die Herkunfts-Kette (sourceElementId) in der Applikationsschicht.
CREATE TABLE "ActualsSet" (
"id" TEXT NOT NULL,
"planId" TEXT NOT NULL,
-- Exaktes Erfassungsdatum: erscheint in der Liste und auf der Zeitachse.
"recordedOn" DATE NOT NULL,
-- Kalenderjahr. NUR dieses geht in die Berechnung ein (der Rechenkern arbeitet in
-- ganzen Jahren ab Planbeginn).
"year" INTEGER NOT NULL,
"comment" TEXT,
-- Effektiver Cash-Bestand. Eigenes Feld, weil Cash kein FinancialElement ist.
"cash" DOUBLE PRECISION,
-- Werte je WURZEL-Element: { "<rootElementId>": { "value": 120000, "mortgage": 400000 } }
"values" JSONB NOT NULL DEFAULT '{}',
"createdById" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ActualsSet_pkey" PRIMARY KEY ("id")
);
-- Die Saetze werden immer nach Jahr sortiert gelesen (aeltester zuerst fuer die Rechnung).
CREATE INDEX "ActualsSet_planId_year_idx" ON "ActualsSet"("planId", "year");
ALTER TABLE "ActualsSet" ADD CONSTRAINT "ActualsSet_planId_fkey"
FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ActualsSet" ADD CONSTRAINT "ActualsSet_createdById_fkey"
FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+31
View File
@@ -34,6 +34,7 @@ model User {
plans Plan[]
versions ScenarioVersion[]
actuals ActualsSet[]
}
enum HouseholdType {
@@ -88,6 +89,36 @@ model Plan {
updatedAt DateTime @updatedAt
scenarios Scenario[]
actuals ActualsSet[]
}
// Ein erfasster Stand der WIRKLICHKEIT zu einem Stichtag (Roadmap Nr. 5).
//
// Haengt am PLAN, nicht am Szenario: Das tatsaechliche PK-Guthaben am 18.8.2026 ist eine
// Zahl, unabhaengig davon, gegen welches Szenario man sie haelt. Die Zuordnung auf die
// szenario-eigenen Element-IDs erfolgt ueber die Herkunfts-Kette (lib/actuals.ts).
model ActualsSet {
id String @id @default(cuid())
planId String
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
// Exaktes Datum fuer Liste und Zeitachse; fuer die Rechnung zaehlt nur `year`.
recordedOn DateTime @db.Date
year Int
comment String?
// Effektiver Cash-Bestand (Cash ist kein FinancialElement).
cash Float?
// Werte je Wurzel-Element: Record<rootElementId, { value?, mortgage? }>
values Json @default("{}")
createdById String
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([planId, year])
}
// Die berechenbare Einheit: Grundprofil + Phasenkette + Elemente. Genau ein Szenario je