Files
FPT/prisma/schema.prisma
T

246 lines
7.8 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)
// Aus Verkaeufen im Uebergang aus der Vorphase verfuegbares Startkapital (wird beim
// Speichern des Uebergangs automatisch gesetzt, siehe PhaseTransition).
incomingCapital Float @default(0)
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)
// Baseline-Wert bei automatischer Uebernahme aus der Vorphase (0 bei manuell angelegten
// Wertschriften). Dient dazu, im UI zu erkennen, wie viel vom verfuegbaren Startkapital
// bereits (on top of der Uebernahme) zugewiesen wurde.
carriedBaseValue Float @default(0)
// Verweist auf die Wertschrift der Vorphase, aus der automatisch uebernommen wurde
// (nur intern zur Deduplizierung bei wiederholtem Speichern des Uebergangs, kein FK).
carriedFromSecurityId String?
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?
}