import { Prisma } from "@prisma/client"; import { NextResponse } from "next/server"; import { z } from "zod"; import { snapshotBudget } from "@/lib/audit-snapshots"; import { createAuditLog } from "@/lib/audit-log"; import { canManageBudgets } from "@/lib/domain"; import prisma from "@/lib/prisma"; import { getCurrentViewer } from "@/lib/session"; const updateBudgetSchema = z .object({ name: z.string().trim().min(2).max(80), totalBudget: z.coerce.number().min(0), releasedAmount: z.coerce.number().min(0).optional(), colorCode: z.string().regex(/^#([0-9a-fA-F]{6})$/) }) .superRefine((value, ctx) => { if (value.releasedAmount !== undefined && value.releasedAmount > value.totalBudget) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Der bereits an die AG uebergebene Betrag darf das Budget nicht uebersteigen.", path: ["releasedAmount"] }); } }); type Context = { params: Promise<{ id: string; }>; }; export async function PATCH(request: Request, { params }: Context) { const { id } = await params; const viewer = await getCurrentViewer(); if (!viewer) { return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }); } if (!canManageBudgets(viewer.role)) { return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Budgets aendern." }, { status: 403 }); } const budget = await prisma.budget.findUnique({ where: { id } }); if (!budget) { return NextResponse.json({ error: "Budget nicht gefunden." }, { status: 404 }); } const body = await request.json().catch(() => null); const parsed = updateBudgetSchema.safeParse(body); if (!parsed.success) { return NextResponse.json({ error: "Budgetname, Betrag, Mitteluebergabe oder Farbe sind ungueltig." }, { status: 400 }); } try { const previousBudget = budget; const nextReleasedAmount = parsed.data.releasedAmount ?? Number(previousBudget.releasedAmount); const updatedBudget = await prisma.budget.update({ where: { id }, data: { name: parsed.data.name, totalBudget: parsed.data.totalBudget, releasedAmount: nextReleasedAmount, colorCode: parsed.data.colorCode } }); await createAuditLog(prisma, { actorId: viewer.id, action: "budget.update", entityType: "budget", entityId: updatedBudget.id, entityLabel: updatedBudget.name, summary: `Budget ${updatedBudget.name} wurde aktualisiert.`, metadata: { totalBudget: parsed.data.totalBudget, releasedAmount: nextReleasedAmount, colorCode: parsed.data.colorCode, rollback: { kind: "budget.update", previous: snapshotBudget(previousBudget), next: snapshotBudget(updatedBudget) } } }); return NextResponse.json({ budget: updatedBudget }); } catch (error) { if ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002" ) { return NextResponse.json( { error: "In dieser AG gibt es bereits ein Budget mit diesem Namen." }, { status: 409 } ); } throw error; } } export async function DELETE(_: Request, { params }: Context) { const { id } = await params; const viewer = await getCurrentViewer(); if (!viewer) { return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }); } if (!canManageBudgets(viewer.role)) { return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Budgets loeschen." }, { status: 403 }); } const budget = await prisma.budget.findUnique({ where: { id }, include: { _count: { select: { expenses: true } } } }); if (!budget) { return NextResponse.json({ error: "Budget nicht gefunden." }, { status: 404 }); } if (budget._count.expenses > 0) { return NextResponse.json( { error: "Dieses Budget enthaelt noch Ausgaben. Bitte loesche oder verschiebe erst die Posten." }, { status: 400 } ); } await prisma.budget.delete({ where: { id } }); await createAuditLog(prisma, { actorId: viewer.id, action: "budget.delete", entityType: "budget", entityId: budget.id, entityLabel: budget.name, summary: `Budget ${budget.name} wurde geloescht.`, metadata: { rollback: { kind: "budget.delete", deleted: snapshotBudget(budget) } } }); return NextResponse.json({ ok: true }); }