Files
FPT/prisma/schema.prisma
T
admGitAICDS dfebbeb397 Drei Berechnungs-Fixes: Phaseninflation, Tilgungsraten, Vorbezugssteuer
- 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>
2026-07-16 22:22:34 +02:00

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])
}