236 lines
7.1 KiB
Plaintext
236 lines
7.1 KiB
Plaintext
// FPT (Financial Planning Tool) — Datenmodell gemaess FDD/TDD Kapitel 2.
|
|
// Get a free hosted Postgres database in seconds: `npx create-db`
|
|
|
|
generator client {
|
|
provider = "prisma-client"
|
|
output = "../src/generated/prisma"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
}
|
|
|
|
// Zugriffsschutz der Webapplikation: kein vorkonfiguriertes Passwort -- der
|
|
// Benutzer legt es beim allerersten Login selbst fest (genau eine Zeile).
|
|
model AppCredential {
|
|
id String @id @default(cuid())
|
|
passwordHash String
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
enum HouseholdType {
|
|
SINGLE
|
|
COUPLE
|
|
}
|
|
|
|
enum PersonRole {
|
|
PERSON_A
|
|
PERSON_B
|
|
}
|
|
|
|
enum IncomeMode {
|
|
PER_PERSON
|
|
HOUSEHOLD
|
|
}
|
|
|
|
enum OwnerTag {
|
|
PERSON_A
|
|
PERSON_B
|
|
HOUSEHOLD
|
|
}
|
|
|
|
enum OneTimeEventType {
|
|
INCOME
|
|
EXPENSE
|
|
}
|
|
|
|
enum TransitionDecision {
|
|
CARRY_OVER
|
|
SELL
|
|
}
|
|
|
|
enum PositionType {
|
|
SECURITY
|
|
REAL_ESTATE
|
|
}
|
|
|
|
// Ein Haushalt (1 oder 2 Personen) - Wurzel-Objekt
|
|
model Household {
|
|
id String @id @default(cuid())
|
|
householdType HouseholdType
|
|
inflationRateDefault Float
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
persons Person[]
|
|
plans Plan[]
|
|
}
|
|
|
|
// Einzelperson im Haushalt (Alter, geplantes Pensionsalter)
|
|
model Person {
|
|
id String @id @default(cuid())
|
|
householdId String
|
|
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
|
|
role PersonRole
|
|
age Int
|
|
retirementAge Int
|
|
|
|
incomeEntries IncomeEntry[]
|
|
retirementInfos RetirementInfo[]
|
|
|
|
@@unique([householdId, role])
|
|
}
|
|
|
|
// Eine vollstaendige Phasenkette; kann Szenario eines anderen Plans sein
|
|
model Plan {
|
|
id String @id @default(cuid())
|
|
householdId String
|
|
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
|
|
name String
|
|
|
|
// Szenario-Verzweigung: ein Szenario ist ein eigener Plan mit Verweis auf den Ursprungsplan
|
|
// und die Phase, ab der die Ketten divergieren (branchFromPhaseId zeigt auf eine Phase
|
|
// dieses neuen Plans, welche die per Deep-Copy duplizierte letzte gemeinsame Phase ist).
|
|
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
|
|
|
|
phases Phase[]
|
|
}
|
|
|
|
// Ein Lebensabschnitt innerhalb eines Plans
|
|
model Phase {
|
|
id String @id @default(cuid())
|
|
planId String
|
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
sequenceNumber Int
|
|
name String
|
|
durationYears Int
|
|
inflationRate Float?
|
|
incomeMode IncomeMode @default(HOUSEHOLD)
|
|
|
|
incomeEntries IncomeEntry[]
|
|
expenseEntries ExpenseEntry[]
|
|
securities Security[]
|
|
realEstates RealEstate[]
|
|
oneTimeEvents OneTimeEvent[]
|
|
retirementInfos RetirementInfo[]
|
|
|
|
// Uebergang IN diese Phase (diese Phase ist Ziel) bzw. AUS dieser Phase (diese Phase ist Quelle)
|
|
transitionIn PhaseTransition? @relation("TransitionTarget")
|
|
transitionOut PhaseTransition? @relation("TransitionSource")
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([planId, sequenceNumber])
|
|
}
|
|
|
|
// Einkommensposten einer Phase (pro Person oder gemeinsam, je nach Phase.incomeMode)
|
|
model IncomeEntry {
|
|
id String @id @default(cuid())
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
personId String?
|
|
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
|
label String?
|
|
amount Float
|
|
}
|
|
|
|
// Ausgabenposten einer Phase (immer Haushaltsebene, generischer Gesamtbetrag)
|
|
model ExpenseEntry {
|
|
id String @id @default(cuid())
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
label String?
|
|
amount Float
|
|
}
|
|
|
|
// Eine Wertschrift innerhalb einer Phase (inkl. jaehrlichem Sparbeitrag)
|
|
model Security {
|
|
id String @id @default(cuid())
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
name String
|
|
startValue Float
|
|
expectedReturn Float
|
|
annualContribution Float @default(0)
|
|
ownerTag OwnerTag @default(HOUSEHOLD)
|
|
// Steuersatz auf Verkaufsgewinn bei Uebernahme in PhaseTransitionItem (Default 0%, siehe Kap. 9)
|
|
saleTaxRate Float @default(0)
|
|
|
|
transitionItems PhaseTransitionItem[]
|
|
}
|
|
|
|
// Eine Immobilie innerhalb einer Phase
|
|
model RealEstate {
|
|
id String @id @default(cuid())
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
name String
|
|
marketValue Float
|
|
mortgage Float
|
|
valueGrowth Float
|
|
amortization Float
|
|
salePrice Float?
|
|
// Geschaetzte Grundstueckgewinnsteuer (%), direkt am ausloesenden Ereignis erfasst (Kap. 9)
|
|
saleTaxRate Float @default(20)
|
|
|
|
transitionItems PhaseTransitionItem[]
|
|
}
|
|
|
|
// Einmalige Sondereinnahme/-ausgabe
|
|
model OneTimeEvent {
|
|
id String @id @default(cuid())
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
type OneTimeEventType
|
|
amount Float
|
|
description String?
|
|
}
|
|
|
|
// Renten-/Kapitalbezugsangaben (nur in Pensionierungsphasen), pro Person
|
|
model RetirementInfo {
|
|
id String @id @default(cuid())
|
|
phaseId String
|
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
|
personId String
|
|
person Person @relation(fields: [personId], references: [id], onDelete: Cascade)
|
|
ahvAmount Float @default(0)
|
|
pkPensionAmount Float @default(0)
|
|
lumpSumAmount Float @default(0)
|
|
// Geschaetzte Kapitalbezugssteuer (%), direkt am auslösenden Ereignis erfasst (Kap. 9)
|
|
lumpSumTaxRate Float @default(8)
|
|
|
|
@@unique([phaseId, personId])
|
|
}
|
|
|
|
// Entscheidungen beim Uebergang zweier Phasen (uebernehmen/verkaufen je Position)
|
|
model PhaseTransition {
|
|
id String @id @default(cuid())
|
|
fromPhaseId String @unique
|
|
fromPhase Phase @relation("TransitionSource", fields: [fromPhaseId], references: [id], onDelete: Cascade)
|
|
toPhaseId String @unique
|
|
toPhase Phase @relation("TransitionTarget", fields: [toPhaseId], references: [id], onDelete: Cascade)
|
|
|
|
items PhaseTransitionItem[]
|
|
}
|
|
|
|
// Einzelentscheidung fuer eine Position (Wertschrift oder Immobilie) beim Phasenuebergang
|
|
model PhaseTransitionItem {
|
|
id String @id @default(cuid())
|
|
transitionId String
|
|
transition PhaseTransition @relation(fields: [transitionId], references: [id], onDelete: Cascade)
|
|
positionType PositionType
|
|
securityId String?
|
|
security Security? @relation(fields: [securityId], references: [id], onDelete: Cascade)
|
|
realEstateId String?
|
|
realEstate RealEstate? @relation(fields: [realEstateId], references: [id], onDelete: Cascade)
|
|
decision TransitionDecision
|
|
salePrice Float?
|
|
}
|