V7: Haushalt auf Plan-Ebene, Annahmen bleiben am Szenario
Deploy App / deploy (push) Successful in 1m52s

Haushaltsform, Personen (Name/Alter) und Startjahr wandern vom Szenario
auf den Plan. Das Pensionsalter bleibt szenario-eigen -- es ist der Kern
jedes Frueh-/Spaetpensionierungs-Szenarios.

Neue Tabelle PlanPerson; Person behaelt nur Rolle + Pensionsalter;
Plan bekommt householdType und startYear.

Der Rechenkern bleibt unberuehrt: toPlanInput fuegt beide Ebenen wieder
zu einem unveraenderten PlanInput zusammen. 43 Golden Tests unveraendert.

Nebeneffekt: Ein Ist-Satz trifft jetzt in ALLEN Szenarien dasselbe
Planjahr -- vorher war das nicht garantiert.

Wiederherstellen einer Version setzt nur noch Szenario-Eigenes zurueck.
Profil-Dialog kennzeichnet plan-weite vs. szenario-eigene Felder.

Zwei Tests spielen echte V6-Daten ein und pruefen die Uebernahme.
Spezifikation 0.23 (210 -> 212).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 21:53:12 +02:00
parent 44c6c81f5d
commit ce5f83823f
10 changed files with 290 additions and 51 deletions
+19 -10
View File
@@ -5,6 +5,10 @@ import type { CashTransitionData, PhaseData, TransitionData } from "@/lib/elemen
import type { PlanInput } from "@/lib/types";
export const planInclude = {
// Der Haushalt (Form, Personen, Startjahr) liegt seit V7 am PLAN -- `toPlanInput` fügt ihn
// mit den szenario-eigenen Pensionsaltern wieder zu einem `PlanInput` zusammen. Der
// Rechenkern sieht davon nichts.
plan: { include: { persons: { orderBy: { role: "asc" } } } },
persons: { orderBy: { role: "asc" } },
phases: { orderBy: { sequenceNumber: "asc" } },
elements: {
@@ -35,17 +39,22 @@ export function toPlanInput(plan: PlanWithRelations): PlanInput {
return {
id: plan.id,
name: plan.name,
householdType: plan.householdType,
householdType: plan.plan.householdType,
inflationRateDefault: plan.inflationRateDefault,
initialCash: plan.initialCash,
startYear: plan.startYear,
persons: plan.persons.map((p) => ({
id: p.id,
role: p.role,
name: p.name,
age: p.age,
retirementAge: p.retirementAge,
})),
startYear: plan.plan.startYear,
// Name und Alter vom Plan, Pensionsalter vom Szenario. Fehlt zu einer Rolle die
// Plan-Person, greift ein Notbehelf -- die Berechnung darf daran nicht scheitern.
persons: plan.persons.map((p) => {
const hh = plan.plan.persons.find((x) => x.role === p.role);
return {
id: p.id,
role: p.role,
name: hh?.name ?? null,
age: hh?.age ?? 0,
retirementAge: p.retirementAge,
};
}),
phases: plan.phases.map((phase) => ({
id: phase.id,
sequenceNumber: phase.sequenceNumber,
@@ -88,7 +97,7 @@ export async function getOwnedScenario(scenarioId: string, userId: string) {
export async function getOwnedScenarioWithMeta(scenarioId: string, userId: string) {
return prisma.scenario.findFirst({
where: { id: scenarioId, plan: { userId } },
include: { ...planInclude, plan: true },
include: planInclude,
});
}