dfebbeb397
- Phase.inflationRate ersatzlos entfernt (inkl. Migration). Die Inflation liegt seit V5 plan-weit; das Feld wurde von computePlan nie gelesen und war wirkungslos. - Amortisation und Tilgung stoppen, sobald Hypothek bzw. Schuld abbezahlt sind: Hypothek/Restschuld sind neu laufende Salden, die Rate ist pro Jahr am Restsaldo gekappt. Belastet danach weder Cash noch Sparquote. - Kapitalbezugssteuer greift neu auch bei PK-/3a-Vorbezuegen vor der Pensionierung. Der Bezug wird brutto dem Kapital entnommen, netto ins Cash gebucht. plannedSaveRate ist neu die Rate des ersten Phasenjahres. Drei Regressionstests ergaenzt (10 -> 13). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
152 lines
4.6 KiB
Plaintext
152 lines
4.6 KiB
Plaintext
// FPT (Financial Planning Tool) — Datenmodell.
|
|
// Kernkonzept (Rework 07/2026): finanzielle Elemente leben auf PLAN-Ebene und sind
|
|
// ueber alle Lebensphasen hinweg dieselbe Entitaet. Pro Element existiert je Lebensphase
|
|
// ein Werte-Datensatz (ElementPhaseValue) und je Uebergang ein Entscheid-Datensatz
|
|
// (ElementTransitionValue). Die kategorie-/kontextspezifischen Felder liegen als JSON,
|
|
// validiert und typisiert in der Applikationsschicht (lib/elements.ts).
|
|
//
|
|
// Rework 07/2026 (V3): Das Grundprofil (Haushaltsform, Personen, Inflation) wurde vom
|
|
// frueheren Household auf die PLAN-Ebene verschoben. Jeder Plan ist selbsttragend und
|
|
// definiert seine eigenen Personen (Alter, Pensionsalter) und Inflationsannahme.
|
|
|
|
generator client {
|
|
provider = "prisma-client"
|
|
output = "../src/generated/prisma"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
username String @unique
|
|
passwordHash String
|
|
createdAt DateTime @default(now())
|
|
|
|
plans Plan[]
|
|
}
|
|
|
|
enum HouseholdType {
|
|
SINGLE
|
|
COUPLE
|
|
}
|
|
|
|
enum PersonRole {
|
|
PERSON_A
|
|
PERSON_B
|
|
}
|
|
|
|
enum OwnerRole {
|
|
PERSON_A
|
|
PERSON_B
|
|
HOUSEHOLD
|
|
}
|
|
|
|
enum ElementCategory {
|
|
INCOME
|
|
EXPENSE
|
|
AHV
|
|
PENSION_FUND
|
|
PILLAR_3A
|
|
REAL_ESTATE
|
|
OTHER_ASSET
|
|
OTHER_DEBT
|
|
}
|
|
|
|
// Einzelperson eines Plans. retirementAge ist das (plan-eigene) Pensionsalter.
|
|
model Person {
|
|
id String @id @default(cuid())
|
|
planId String
|
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
role PersonRole
|
|
name String?
|
|
age Int
|
|
retirementAge Int
|
|
|
|
@@unique([planId, role])
|
|
}
|
|
|
|
// Eine vollstaendige Phasenkette; selbsttragend inkl. Grundprofil (Haushaltsform,
|
|
// Personen, Inflationsannahme). Kann Szenario eines anderen Plans sein (Deep-Copy).
|
|
model Plan {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
name String
|
|
householdType HouseholdType
|
|
inflationRateDefault Float
|
|
initialCash Float @default(0)
|
|
|
|
parentPlanId String?
|
|
parentPlan Plan? @relation("PlanScenarios", fields: [parentPlanId], references: [id], onDelete: SetNull)
|
|
scenarios Plan[] @relation("PlanScenarios")
|
|
branchFromPhaseId String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
persons Person[]
|
|
phases Phase[]
|
|
elements FinancialElement[]
|
|
}
|
|
|
|
// Ein Lebensabschnitt innerhalb eines Plans. Der Phasentyp (Erwerb/Pension/Mischung)
|
|
// wird NICHT gespeichert, sondern aus Alter + Pensionsalter abgeleitet (lib/calculations).
|
|
// Die Inflation liegt seit V5 plan-weit am Plan (inflationRateDefault), nicht mehr hier.
|
|
model Phase {
|
|
id String @id @default(cuid())
|
|
planId String
|
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
sequenceNumber Int
|
|
name String
|
|
durationYears Int
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
phaseValues ElementPhaseValue[]
|
|
transitionValues ElementTransitionValue[] @relation("TransitionFromPhase")
|
|
|
|
@@unique([planId, sequenceNumber])
|
|
}
|
|
|
|
// Ein finanzielles Element (plan-weit): Kategorie + optionale Personenzuordnung.
|
|
model FinancialElement {
|
|
id String @id @default(cuid())
|
|
planId String
|
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
category ElementCategory
|
|
name String
|
|
ownerRole OwnerRole?
|
|
orderIndex Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
|
|
phaseValues ElementPhaseValue[]
|
|
transitionValues ElementTransitionValue[]
|
|
}
|
|
|
|
// Werte eines Elements INNERHALB einer Lebensphase (kategorie-/kontextspezifisch, JSON).
|
|
model ElementPhaseValue {
|
|
id String @id @default(cuid())
|
|
elementId String
|
|
element FinancialElement @relation(fields: [elementId], references: [id], onDelete: Cascade)
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
data Json
|
|
|
|
@@unique([elementId, phaseId])
|
|
}
|
|
|
|
// Entscheid/Werte eines Elements beim UEBERGANG nach der Phase fromPhase (JSON).
|
|
model ElementTransitionValue {
|
|
id String @id @default(cuid())
|
|
elementId String
|
|
element FinancialElement @relation(fields: [elementId], references: [id], onDelete: Cascade)
|
|
fromPhaseId String
|
|
fromPhase Phase @relation("TransitionFromPhase", fields: [fromPhaseId], references: [id], onDelete: Cascade)
|
|
data Json
|
|
|
|
@@unique([elementId, fromPhaseId])
|
|
}
|