Initial commit: FPT Financial Planning Tool
Deploy App / deploy (push) Successful in 3m3s

This commit is contained in:
2026-07-08 19:19:39 +02:00
commit 4f990c9686
60 changed files with 12436 additions and 0 deletions
@@ -0,0 +1,245 @@
-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "public";
-- CreateEnum
CREATE TYPE "HouseholdType" AS ENUM ('SINGLE', 'COUPLE');
-- CreateEnum
CREATE TYPE "PersonRole" AS ENUM ('PERSON_A', 'PERSON_B');
-- CreateEnum
CREATE TYPE "IncomeMode" AS ENUM ('PER_PERSON', 'HOUSEHOLD');
-- CreateEnum
CREATE TYPE "OwnerTag" AS ENUM ('PERSON_A', 'PERSON_B', 'HOUSEHOLD');
-- CreateEnum
CREATE TYPE "OneTimeEventType" AS ENUM ('INCOME', 'EXPENSE');
-- CreateEnum
CREATE TYPE "TransitionDecision" AS ENUM ('CARRY_OVER', 'SELL');
-- CreateEnum
CREATE TYPE "PositionType" AS ENUM ('SECURITY', 'REAL_ESTATE');
-- CreateTable
CREATE TABLE "AppCredential" (
"id" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AppCredential_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Household" (
"id" TEXT NOT NULL,
"householdType" "HouseholdType" NOT NULL,
"inflationRateDefault" DOUBLE PRECISION NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Household_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Person" (
"id" TEXT NOT NULL,
"householdId" TEXT NOT NULL,
"role" "PersonRole" NOT NULL,
"age" INTEGER NOT NULL,
"retirementAge" INTEGER NOT NULL,
CONSTRAINT "Person_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Plan" (
"id" TEXT NOT NULL,
"householdId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"parentPlanId" TEXT,
"branchFromPhaseId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Plan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Phase" (
"id" TEXT NOT NULL,
"planId" TEXT NOT NULL,
"sequenceNumber" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"durationYears" INTEGER NOT NULL,
"inflationRate" DOUBLE PRECISION,
"incomeMode" "IncomeMode" NOT NULL DEFAULT 'HOUSEHOLD',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Phase_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "IncomeEntry" (
"id" TEXT NOT NULL,
"phaseId" TEXT NOT NULL,
"personId" TEXT,
"label" TEXT,
"amount" DOUBLE PRECISION NOT NULL,
CONSTRAINT "IncomeEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ExpenseEntry" (
"id" TEXT NOT NULL,
"phaseId" TEXT NOT NULL,
"label" TEXT,
"amount" DOUBLE PRECISION NOT NULL,
CONSTRAINT "ExpenseEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Security" (
"id" TEXT NOT NULL,
"phaseId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"startValue" DOUBLE PRECISION NOT NULL,
"expectedReturn" DOUBLE PRECISION NOT NULL,
"annualContribution" DOUBLE PRECISION NOT NULL DEFAULT 0,
"ownerTag" "OwnerTag" NOT NULL DEFAULT 'HOUSEHOLD',
"saleTaxRate" DOUBLE PRECISION NOT NULL DEFAULT 0,
CONSTRAINT "Security_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RealEstate" (
"id" TEXT NOT NULL,
"phaseId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"marketValue" DOUBLE PRECISION NOT NULL,
"mortgage" DOUBLE PRECISION NOT NULL,
"valueGrowth" DOUBLE PRECISION NOT NULL,
"amortization" DOUBLE PRECISION NOT NULL,
"salePrice" DOUBLE PRECISION,
"saleTaxRate" DOUBLE PRECISION NOT NULL DEFAULT 20,
CONSTRAINT "RealEstate_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OneTimeEvent" (
"id" TEXT NOT NULL,
"phaseId" TEXT NOT NULL,
"type" "OneTimeEventType" NOT NULL,
"amount" DOUBLE PRECISION NOT NULL,
"description" TEXT,
CONSTRAINT "OneTimeEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RetirementInfo" (
"id" TEXT NOT NULL,
"phaseId" TEXT NOT NULL,
"personId" TEXT NOT NULL,
"ahvAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"pkPensionAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"lumpSumAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"lumpSumTaxRate" DOUBLE PRECISION NOT NULL DEFAULT 8,
CONSTRAINT "RetirementInfo_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PhaseTransition" (
"id" TEXT NOT NULL,
"fromPhaseId" TEXT NOT NULL,
"toPhaseId" TEXT NOT NULL,
CONSTRAINT "PhaseTransition_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PhaseTransitionItem" (
"id" TEXT NOT NULL,
"transitionId" TEXT NOT NULL,
"positionType" "PositionType" NOT NULL,
"securityId" TEXT,
"realEstateId" TEXT,
"decision" "TransitionDecision" NOT NULL,
"salePrice" DOUBLE PRECISION,
CONSTRAINT "PhaseTransitionItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Person_householdId_role_key" ON "Person"("householdId", "role");
-- CreateIndex
CREATE UNIQUE INDEX "Phase_planId_sequenceNumber_key" ON "Phase"("planId", "sequenceNumber");
-- CreateIndex
CREATE UNIQUE INDEX "RetirementInfo_phaseId_personId_key" ON "RetirementInfo"("phaseId", "personId");
-- CreateIndex
CREATE UNIQUE INDEX "PhaseTransition_fromPhaseId_key" ON "PhaseTransition"("fromPhaseId");
-- CreateIndex
CREATE UNIQUE INDEX "PhaseTransition_toPhaseId_key" ON "PhaseTransition"("toPhaseId");
-- AddForeignKey
ALTER TABLE "Person" ADD CONSTRAINT "Person_householdId_fkey" FOREIGN KEY ("householdId") REFERENCES "Household"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Plan" ADD CONSTRAINT "Plan_householdId_fkey" FOREIGN KEY ("householdId") REFERENCES "Household"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Plan" ADD CONSTRAINT "Plan_parentPlanId_fkey" FOREIGN KEY ("parentPlanId") REFERENCES "Plan"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Phase" ADD CONSTRAINT "Phase_planId_fkey" FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IncomeEntry" ADD CONSTRAINT "IncomeEntry_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IncomeEntry" ADD CONSTRAINT "IncomeEntry_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ExpenseEntry" ADD CONSTRAINT "ExpenseEntry_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Security" ADD CONSTRAINT "Security_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RealEstate" ADD CONSTRAINT "RealEstate_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OneTimeEvent" ADD CONSTRAINT "OneTimeEvent_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RetirementInfo" ADD CONSTRAINT "RetirementInfo_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RetirementInfo" ADD CONSTRAINT "RetirementInfo_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhaseTransition" ADD CONSTRAINT "PhaseTransition_fromPhaseId_fkey" FOREIGN KEY ("fromPhaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhaseTransition" ADD CONSTRAINT "PhaseTransition_toPhaseId_fkey" FOREIGN KEY ("toPhaseId") REFERENCES "Phase"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhaseTransitionItem" ADD CONSTRAINT "PhaseTransitionItem_transitionId_fkey" FOREIGN KEY ("transitionId") REFERENCES "PhaseTransition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhaseTransitionItem" ADD CONSTRAINT "PhaseTransitionItem_securityId_fkey" FOREIGN KEY ("securityId") REFERENCES "Security"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PhaseTransitionItem" ADD CONSTRAINT "PhaseTransitionItem_realEstateId_fkey" FOREIGN KEY ("realEstateId") REFERENCES "RealEstate"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
+235
View File
@@ -0,0 +1,235 @@
// 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?
}