"use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { z } from "zod"; import { prisma } from "@/server/db"; import { moduleGuard } from "@/server/action-guard"; import { writeAuditLog } from "@/server/audit"; import { withQuery } from "@/lib/supplier"; const guard = moduleGuard("suppliers"); const level = z.coerce.number().int().min(1).max(4); const softwareSchema = z.object({ name: z.string().trim().min(1).max(200), confidentiality: level, integrity: level, availability: level, criticality: level, providerAssetId: z.string().optional(), version: z.string().trim().max(100).optional(), approvalStatus: z.enum(["BEANTRAGT", "FREIGEGEBEN", "GESPERRT"]), approvedBy: z.string().trim().max(200).optional(), nextReview: z.string().optional(), notes: z.string().trim().max(5000).optional(), }); function parse(formData: FormData) { const p = softwareSchema.parse({ name: formData.get("name"), confidentiality: formData.get("confidentiality"), integrity: formData.get("integrity"), availability: formData.get("availability"), criticality: formData.get("criticality"), providerAssetId: formData.get("providerAssetId") || undefined, version: formData.get("version") || undefined, approvalStatus: formData.get("approvalStatus") || "BEANTRAGT", approvedBy: formData.get("approvedBy") || undefined, nextReview: formData.get("nextReview") || undefined, notes: formData.get("notes") || undefined, }); return { asset: { name: p.name, confidentiality: p.confidentiality, integrity: p.integrity, availability: p.availability }, profile: { criticality: p.criticality, providerAssetId: p.providerAssetId || null, version: p.version || null, approvalStatus: p.approvalStatus, approvedBy: p.approvedBy || null, nextReview: p.nextReview ? new Date(p.nextReview) : null, notes: p.notes || null, }, }; } export async function createSoftware(formData: FormData) { const { session, db } = await guard("supplier:write"); const { asset, profile } = parse(formData); const created = await db.asset.create({ data: { ...asset, type: "SOFTWARE", tenantId: session.user.tenantId, createdBy: session.user.id }, }); const last = await prisma.softwareProfile.aggregate({ where: { tenantId: session.user.tenantId }, _max: { refNo: true } }); await db.softwareProfile.create({ data: { ...profile, assetId: created.id, refNo: (last._max.refNo ?? 0) + 1, tenantId: session.user.tenantId, createdBy: session.user.id }, }); await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "software", entityId: created.id, after: { ...asset, ...profile } }); revalidatePath("/suppliers"); revalidatePath("/assets"); redirect(`/suppliers?tab=software&edit=${created.id}`); } export async function updateSoftware(assetId: string, formData: FormData) { const { session, db } = await guard("supplier:write"); const before = await db.asset.findUnique({ where: { id: assetId }, include: { softwareProfile: true } }); if (!before) throw new Error("Software nicht gefunden"); const { asset, profile } = parse(formData); await db.asset.update({ where: { id: assetId }, data: asset }); await db.softwareProfile.update({ where: { assetId }, data: profile }); await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "software", entityId: assetId, before, after: { ...asset, ...profile } }); revalidatePath("/suppliers"); revalidatePath("/assets"); const returnTo = (formData.get("returnTo") as string) || "/suppliers?tab=software"; redirect(withQuery(returnTo, "detail", assetId)); } export async function deleteSoftware(assetId: string, returnTo: string = "/suppliers?tab=software") { const { session, db } = await guard("supplier:write"); const before = await db.asset.findUnique({ where: { id: assetId } }); if (!before) throw new Error("Software nicht gefunden"); await db.asset.delete({ where: { id: assetId } }); await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "software", entityId: assetId, before }); revalidatePath("/suppliers"); revalidatePath("/assets"); redirect(returnTo); }