Rework core model: financial elements as plan-wide entities across phases; derived phase types (Erwerb/Pension/Misch) with retirement-capped durations and per-plan retirement age; AHV (gap years + couple ceiling), PK payout/annuity, 3a, real estate, other assets/debts; horizontal timeline with retirement markers; phase x element matrix with detail panel; savings/consumption quota + available-capital key figures with red status
Deploy App / deploy (push) Successful in 1m57s

This commit is contained in:
2026-07-13 07:55:58 +02:00
parent b3a3b565ac
commit b775ab77cb
27 changed files with 2407 additions and 2166 deletions
@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { getOwnedElement } from "@/lib/queries";
import { getCurrentUserId } from "@/lib/session";
import { phaseDataSchema } from "@/lib/elements";
// Speichert die Werte eines Elements innerhalb einer Lebensphase (Upsert).
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ elementId: string; phaseId: string }> }
) {
const userId = await getCurrentUserId();
if (!userId) return NextResponse.json({ error: "Nicht authentifiziert." }, { status: 401 });
const { elementId, phaseId } = await params;
const element = await getOwnedElement(elementId, userId);
if (!element) return NextResponse.json({ error: "Element nicht gefunden." }, { status: 404 });
const phase = await prisma.phase.findFirst({ where: { id: phaseId, planId: element.planId } });
if (!phase) return NextResponse.json({ error: "Phase nicht gefunden." }, { status: 404 });
const body = await request.json();
const parsed = phaseDataSchema.safeParse(body);
if (!parsed.success) return NextResponse.json({ error: "Ungueltige Eingabe." }, { status: 400 });
await prisma.elementPhaseValue.upsert({
where: { elementId_phaseId: { elementId, phaseId } },
create: { elementId, phaseId, data: parsed.data },
update: { data: parsed.data },
});
return NextResponse.json({ ok: true });
}
+41
View File
@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { prisma } from "@/lib/db";
import { getOwnedElement } from "@/lib/queries";
import { getCurrentUserId } from "@/lib/session";
const patchSchema = z.object({ name: z.string().min(1).max(120) });
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ elementId: string }> }
) {
const userId = await getCurrentUserId();
if (!userId) return NextResponse.json({ error: "Nicht authentifiziert." }, { status: 401 });
const { elementId } = await params;
const element = await getOwnedElement(elementId, userId);
if (!element) return NextResponse.json({ error: "Element nicht gefunden." }, { status: 404 });
const body = await request.json();
const parsed = patchSchema.safeParse(body);
if (!parsed.success) return NextResponse.json({ error: "Ungueltige Eingabe." }, { status: 400 });
await prisma.financialElement.update({ where: { id: element.id }, data: { name: parsed.data.name } });
return NextResponse.json({ ok: true });
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ elementId: string }> }
) {
const userId = await getCurrentUserId();
if (!userId) return NextResponse.json({ error: "Nicht authentifiziert." }, { status: 401 });
const { elementId } = await params;
const element = await getOwnedElement(elementId, userId);
if (!element) return NextResponse.json({ error: "Element nicht gefunden." }, { status: 404 });
await prisma.financialElement.delete({ where: { id: element.id } });
return NextResponse.json({ ok: true });
}
@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { getOwnedElement } from "@/lib/queries";
import { getCurrentUserId } from "@/lib/session";
import { transitionDataSchema } from "@/lib/elements";
// Speichert den Uebergangs-Entscheid eines Elements nach der Phase fromPhase (Upsert).
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ elementId: string; fromPhaseId: string }> }
) {
const userId = await getCurrentUserId();
if (!userId) return NextResponse.json({ error: "Nicht authentifiziert." }, { status: 401 });
const { elementId, fromPhaseId } = await params;
const element = await getOwnedElement(elementId, userId);
if (!element) return NextResponse.json({ error: "Element nicht gefunden." }, { status: 404 });
const phase = await prisma.phase.findFirst({ where: { id: fromPhaseId, planId: element.planId } });
if (!phase) return NextResponse.json({ error: "Phase nicht gefunden." }, { status: 404 });
const body = await request.json();
const parsed = transitionDataSchema.safeParse(body);
if (!parsed.success) return NextResponse.json({ error: "Ungueltige Eingabe." }, { status: 400 });
await prisma.elementTransitionValue.upsert({
where: { elementId_fromPhaseId: { elementId, fromPhaseId } },
create: { elementId, fromPhaseId, data: parsed.data },
update: { data: parsed.data },
});
return NextResponse.json({ ok: true });
}