import { Prisma } from "@/generated/prisma/client"; import { prisma } from "@/lib/db"; import { cashTransitionSchema, phaseDataSchema, transitionDataSchema } from "@/lib/elements"; import { retirementDecisionSchema } from "@/lib/retirement-decision"; import { normalizeProgress } from "@/lib/assistant"; import type { CashTransitionData, PhaseData, TransitionData } from "@/lib/elements"; import type { RetirementDecision } from "@/lib/retirement-decision"; 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: { orderBy: { orderIndex: "asc" }, include: { phaseValues: true, transitionValues: true }, }, } satisfies Prisma.ScenarioInclude; export type PlanWithRelations = Prisma.ScenarioGetPayload<{ include: typeof planInclude }>; function parsePhaseData(raw: unknown): PhaseData { const parsed = phaseDataSchema.safeParse(raw); return parsed.success ? parsed.data : {}; } function parseTransitionData(raw: unknown): TransitionData { const parsed = transitionDataSchema.safeParse(raw); return parsed.success ? parsed.data : {}; } function parseRetirementDecision(raw: unknown): RetirementDecision { const parsed = retirementDecisionSchema.safeParse(raw); return parsed.success ? parsed.data : {}; } function parseCashTransition(raw: unknown): CashTransitionData { const parsed = cashTransitionSchema.safeParse(raw); return parsed.success ? parsed.data : {}; } // Wandelt ein Szenario (DB) in die berechenbare Einheit (PlanInput) um. export function toPlanInput(plan: PlanWithRelations): PlanInput { return { id: plan.id, name: plan.name, householdType: plan.plan.householdType, inflationRateDefault: plan.inflationRateDefault, initialCash: plan.initialCash, startYear: plan.plan.startYear, planningHorizonYears: plan.planningHorizonYears, assistantProgress: normalizeProgress(plan.assistantProgress), // 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, name: phase.name, durationYears: phase.durationYears, cashTransition: parseCashTransition(phase.cashTransition), sourcePhaseId: phase.sourcePhaseId, })), elements: plan.elements.map((e) => { const phaseValues: Record = {}; for (const pv of e.phaseValues) phaseValues[pv.phaseId] = parsePhaseData(pv.data); const transitionValues: Record = {}; for (const tv of e.transitionValues) transitionValues[tv.fromPhaseId] = parseTransitionData(tv.data); return { id: e.id, category: e.category, name: e.name, ownerRole: e.ownerRole, orderIndex: e.orderIndex, phaseValues, transitionValues, retirementDecision: parseRetirementDecision(e.retirementDecision), baseData: parsePhaseData(e.baseData), sourceElementId: e.sourceElementId, }; }), }; } // --- Ownership-Abfragen. Der Eigentümer hängt neu am PLAN; Szenario/Phase/Element // erben ihn über die Kette Scenario -> Plan -> User. --- // Lädt ein Szenario inkl. Profil + Phasen + Elemente, nur wenn es dem Benutzer gehört. export async function getOwnedScenario(scenarioId: string, userId: string) { return prisma.scenario.findFirst({ where: { id: scenarioId, plan: { userId } }, include: planInclude, }); } // Wie oben, zusätzlich mit den Kopfdaten (Plan, Basis-Flag, Elternteil). export async function getOwnedScenarioWithMeta(scenarioId: string, userId: string) { return prisma.scenario.findFirst({ where: { id: scenarioId, plan: { userId } }, include: planInclude, }); } // Lädt einen Plan (Behälter) inkl. Szenario-Kopfdaten. export async function getOwnedPlan(planId: string, userId: string) { return prisma.plan.findFirst({ where: { id: planId, userId }, include: { scenarios: { orderBy: { createdAt: "asc" } } }, }); } // Lädt eine Phase (Basisdaten), aber nur wenn sie dem Benutzer gehört. export async function getOwnedPhase(phaseId: string, userId: string) { return prisma.phase.findFirst({ where: { id: phaseId, scenario: { plan: { userId } } }, }); } // Lädt ein Element (Basisdaten), aber nur wenn es dem Benutzer gehört. export async function getOwnedElement(elementId: string, userId: string) { return prisma.financialElement.findFirst({ where: { id: elementId, scenario: { plan: { userId } } }, }); }