V3: 3 Farbschemata, Grundprofil auf Plan-Ebene, Erstellungs-Popups, integer-Zahlenfelder mit Beschleunigungs-Spinner, Carry-Forward des Zielwerts, gefuehrter Uebergang
Deploy App / deploy (push) Successful in 1m51s

- Theming: semantische CSS-Tokens + 3 waehlbare Schemata (Hell/Dunkel/Warm/Sunset), Umschalter im Profil-Menue, FOUC-frei via Inline-Script, localStorage; Klassen-Sweep aller Komponenten, Recharts aus Tokens
- Datenmodell: Household entfaellt; Plan traegt Haushaltsform/Personen/Inflation selbst (Person -> planId, Plan -> userId); destruktive Migration (TRUNCATE); Onboarding/HouseholdSettings entfernt; Plan-Erstellung & -Einstellungen mit Profilfeldern
- Popups: Element-Erstellung mit Inline-Feldern (geteilte ElementPhaseFields/ElementTransitionFields), Phase- und Plan-Popups mit Direkteingabe
- Zahlenfelder: 1'000er-Runden entfernt (floorToThousand/roundToHundred weg), integer MoneyInput mit beschleunigendem Press-and-Hold-Spinner, 0-Bug-Fix, harte Live-Caps
- Quote: Amortisation + Tilgung neu quotenwirksam; Restquote sichtbar (sinkt beim Verteilen); Invest-Deckel = verfuegbares Kapital + fortgeschriebener Zielwert
- Carry-Forward: Startwert der Folgephase = Zielwert der Vorphase minus Uebergangs-Bezug (live abgeleitet); optionale Zusatzinvestition aus verfuegbarem Kapital
- Matrix: Zelle zeigt Start -> Ziel; Uebergangs-Spaltenkopf mit "n offen"-Badge + gefuehrtem Pruef-Panel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 14:49:42 +02:00
parent b775ab77cb
commit 32adfb4476
32 changed files with 1706 additions and 1290 deletions
@@ -0,0 +1,40 @@
-- V3-Rework: Das Grundprofil (Haushaltsform, Personen, Inflation) wandert vom Household
-- auf die Plan-Ebene. Bestehende Plaene/Personen sind ohne Household-Bezug bzw. ohne die
-- neuen Pflichtfelder nicht migrierbar -- die (disponiblen) Testdaten werden verworfen.
TRUNCATE TABLE "Person", "Plan" CASCADE;
-- DropForeignKey
ALTER TABLE "Household" DROP CONSTRAINT "Household_userId_fkey";
-- DropForeignKey
ALTER TABLE "Person" DROP CONSTRAINT "Person_householdId_fkey";
-- DropForeignKey
ALTER TABLE "Plan" DROP CONSTRAINT "Plan_householdId_fkey";
-- DropIndex
DROP INDEX "Person_householdId_role_key";
-- AlterTable
ALTER TABLE "Person" DROP COLUMN "householdId",
ADD COLUMN "planId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "Plan" DROP COLUMN "householdId",
DROP COLUMN "retirementAgeA",
DROP COLUMN "retirementAgeB",
ADD COLUMN "householdType" "HouseholdType" NOT NULL,
ADD COLUMN "inflationRateDefault" DOUBLE PRECISION NOT NULL,
ADD COLUMN "userId" TEXT NOT NULL;
-- DropTable
DROP TABLE "Household";
-- CreateIndex
CREATE UNIQUE INDEX "Person_planId_role_key" ON "Person"("planId", "role");
-- AddForeignKey
ALTER TABLE "Person" ADD CONSTRAINT "Person_planId_fkey" FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Plan" ADD CONSTRAINT "Plan_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+18 -28
View File
@@ -4,6 +4,10 @@
// 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"
@@ -20,7 +24,7 @@ model User {
passwordHash String
createdAt DateTime @default(now())
households Household[]
plans Plan[]
}
enum HouseholdType {
@@ -50,42 +54,27 @@ enum ElementCategory {
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).
// Einzelperson eines Plans. retirementAge ist das (plan-eigene) Pensionsalter.
model Person {
id String @id @default(cuid())
householdId String
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
planId String
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
role PersonRole
age Int
retirementAge Int
@@unique([householdId, role])
@@unique([planId, 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.
// Eine vollstaendige Phasenkette; selbsttragend inkl. Grundprofil (Haushaltsform,
// Personen, Inflationsannahme). Kann Szenario eines anderen Plans sein (Deep-Copy).
model Plan {
id String @id @default(cuid())
householdId String
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
name String
retirementAgeA Int?
retirementAgeB Int?
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
householdType HouseholdType
inflationRateDefault Float
parentPlanId String?
parentPlan Plan? @relation("PlanScenarios", fields: [parentPlanId], references: [id], onDelete: SetNull)
@@ -95,6 +84,7 @@ model Plan {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
persons Person[]
phases Phase[]
elements FinancialElement[]
}