Files
FPT/src/app/api/scenarios/[scenarioId]/copy/route.ts
T
admGitAICDS f22b0a3f27
Deploy App / deploy (push) Successful in 1m55s
Uebersicht der offenen Punkte statt Assistent, Bestaetigung je Phasenzelle
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 15:02:15 +02:00

97 lines
3.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { prisma } from "@/lib/db";
import { getOwnedScenario } from "@/lib/queries";
import { getCurrentUserId } from "@/lib/session";
import { touchScenario } from "@/lib/versioning-db";
const copySchema = z.object({ name: z.string().min(1).max(120) });
// Erstellt ein neues Szenario als vollständige Kopie eines bestehenden. Das Original wird
// zum Elternteil -- damit hängt der Baum in der Seitenleiste und der Diff hat seine Basis.
// Jede kopierte Phase/jedes kopierte Element trägt einen Herkunfts-Verweis auf sein
// Gegenstück im Original; darauf beruht die Abweichungs-Markierung im UI.
export async function POST(request: NextRequest, { params }: { params: Promise<{ scenarioId: string }> }) {
const userId = await getCurrentUserId();
if (!userId) return NextResponse.json({ error: "Nicht authentifiziert." }, { status: 401 });
const { scenarioId } = await params;
const parsed = copySchema.safeParse(await request.json());
if (!parsed.success) return NextResponse.json({ error: "Ungültige Eingabe." }, { status: 400 });
const source = await getOwnedScenario(scenarioId, userId);
if (!source) return NextResponse.json({ error: "Ursprungs-Szenario nicht gefunden." }, { status: 404 });
const newId = await prisma.$transaction(async (tx) => {
const created = await tx.scenario.create({
data: {
planId: source.planId,
name: parsed.data.name,
isBase: false,
parentScenarioId: source.id,
// Haushaltsform, Personen und Startjahr liegen am Plan und werden mitbenutzt, nicht
// kopiert. Szenario-eigen sind nur Inflation, Cash-Anfangswert und Pensionsalter.
inflationRateDefault: source.inflationRateDefault,
initialCash: source.initialCash,
persons: {
create: source.persons.map((p) => ({ role: p.role, retirementAge: p.retirementAge })),
},
},
});
// Phasen kopieren (alte -> neue Id merken, für die Werte-Zuordnung).
const phaseIdMap = new Map<string, string>();
for (const phase of source.phases) {
const p = await tx.phase.create({
data: {
scenarioId: created.id,
sequenceNumber: phase.sequenceNumber,
name: phase.name,
durationYears: phase.durationYears,
cashTransition: phase.cashTransition ?? undefined,
sourcePhaseId: phase.id,
},
});
phaseIdMap.set(phase.id, p.id);
}
// Elemente inkl. Phasen- und Übergangswerten kopieren.
for (const el of source.elements) {
const newEl = await tx.financialElement.create({
data: {
scenarioId: created.id,
category: el.category,
name: el.name,
ownerRole: el.ownerRole,
orderIndex: el.orderIndex,
// Ohne das waere die Kopie eines Szenarios genau fuer den Zweck unbrauchbar, fuer
// den man sie am haeufigsten anlegt: ein anderes Pensionierungs-Szenario.
retirementDecision: el.retirementDecision ?? undefined,
baseData: el.baseData ?? undefined,
sourceElementId: el.id,
},
});
for (const pv of el.phaseValues) {
const phaseId = phaseIdMap.get(pv.phaseId);
if (!phaseId) continue;
await tx.elementPhaseValue.create({
data: { elementId: newEl.id, phaseId, data: pv.data as object },
});
}
for (const tv of el.transitionValues) {
const fromPhaseId = phaseIdMap.get(tv.fromPhaseId);
if (!fromPhaseId) continue;
await tx.elementTransitionValue.create({
data: { elementId: newEl.id, fromPhaseId, data: tv.data as object },
});
}
}
return created.id;
});
// Das kopierte Szenario startet mit einer eigenen Version 1.0.
await touchScenario(newId, userId);
return NextResponse.json({ scenarioId: newId }, { status: 201 });
}