// 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). 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()) households Household[] } 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 } // Ein Haushalt (1 oder 2 Personen), gehoert genau einem Benutzer. model Household { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) householdType HouseholdType inflationRateDefault Float createdAt DateTime @default(now()) updatedAt DateTime @updatedAt persons Person[] plans Plan[] } // Einzelperson im Haushalt. retirementAge ist die Standard-Annahme (im Plan uebersteuerbar). model Person { id String @id @default(cuid()) householdId String household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) role PersonRole age Int retirementAge Int @@unique([householdId, role]) } // Eine vollstaendige Phasenkette; kann Szenario eines anderen Plans sein. // retirementAgeA/B uebersteuern das Pensionsalter der jeweiligen Person NUR fuer diesen // Plan (null = Standard aus Person.retirementAge) -- ermoeglicht Fruehpensions-Szenarien. model Plan { id String @id @default(cuid()) householdId String household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) name String retirementAgeA Int? retirementAgeB Int? 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[] elements FinancialElement[] } // Ein Lebensabschnitt innerhalb eines Plans. Der Phasentyp (Erwerb/Pension/Mischung) // wird NICHT gespeichert, sondern aus Alter + Pensionsalter abgeleitet (lib/calculations). 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? 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]) }