"use server"; import { redirect } from "next/navigation"; import { revalidatePath } from "next/cache"; import { prisma } from "@/lib/prisma"; import { logAdminAction } from "@/lib/audit-log"; import { isValidUploadPath } from "@/lib/uploads"; import { requireAdmin, fail, str, num, strArray } from "./_shared"; export async function createHarvestAction(formData: FormData): Promise { const admin = await requireAdmin(); const year = num(formData, "year"); const type = str(formData, "type"); const slug = `${type.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${year}`; // Checkbox: nur in formData vorhanden, wenn angehakt — fehlt sie ganz // (kein Feld im Formular), gilt "veroeffentlicht" als sicherer Default fuer // manuell im Admin-Bereich angelegte Ernten (bisheriges Verhalten vor // Einfuehrung von isPublished). const isPublished = !formData.has("isPublished") || formData.get("isPublished") === "on"; const harvest = await prisma.harvest.create({ data: { year, type, slug, harvestDate: new Date(str(formData, "harvestDate")), location: str(formData, "location") || "Altenmünster", description: str(formData, "description"), images: strArray(formData, "images").filter(isValidUploadPath), isPublished, }, }); await logAdminAction(admin, { action: "harvest.create", entityType: "Harvest", entityId: harvest.id, detail: `${type} ${year}`, }); revalidatePath("/admin/ernten"); revalidatePath("/ernten"); redirect("/admin/ernten"); } export async function updateHarvestAction(formData: FormData): Promise { const admin = await requireAdmin(); const id = str(formData, "id"); const isPublished = formData.get("isPublished") === "on"; await prisma.harvest.update({ where: { id }, data: { year: num(formData, "year"), type: str(formData, "type"), harvestDate: new Date(str(formData, "harvestDate")), location: str(formData, "location") || "Altenmünster", description: str(formData, "description"), images: strArray(formData, "images").filter(isValidUploadPath), isPublished, }, }); await logAdminAction(admin, { action: "harvest.update", entityType: "Harvest", entityId: id }); revalidatePath("/admin/ernten"); revalidatePath("/ernten"); redirect("/admin/ernten"); } /** * Loescht eine Ernte unwiderruflich. harvestId ist auf Batch UND * harvestId/batchId auf Product jeweils onDelete: Restrict (siehe * prisma/schema.prisma) — die Datenbank wuerde ein direktes delete() also * ohnehin ablehnen, sobald noch Chargen oder Produkte auf diese Ernte * verweisen. Die Zaehlung hier VOR dem Loeschversuch liefert dafuer eine * konkrete, verstaendliche Fehlermeldung statt eines rohen Datenbankfehlers. */ export async function deleteHarvestAction(formData: FormData): Promise { const admin = await requireAdmin(); const id = str(formData, "id"); const harvest = await prisma.harvest.findUnique({ where: { id }, include: { _count: { select: { batches: true, products: true } } }, }); if (!harvest) fail("/admin/ernten", "Ernte nicht gefunden."); if (harvest._count.batches > 0 || harvest._count.products > 0) { fail( "/admin/ernten", `Diese Ernte hat noch ${harvest._count.batches} Charge(n) und ${harvest._count.products} Produkt(e). Lösche oder verschiebe diese zuerst.` ); } await prisma.harvest.delete({ where: { id } }); await logAdminAction(admin, { action: "harvest.delete", entityType: "Harvest", entityId: id, detail: `${harvest.type} ${harvest.year}`, }); revalidatePath("/admin/ernten"); revalidatePath("/ernten"); redirect("/admin/ernten"); } /** * Isolierte "Veroeffentlichen"/"Verbergen"-Aktion, getrennt von * updateHarvestAction — so kann der Status per Knopfdruck direkt aus der * Ernten-Liste umgeschaltet werden, ohne durch das komplette Bearbeiten- * Formular zu muessen (insbesondere fuer den "veroeffentlichen"-Bestaetigungs- * schritt bei aus ApiCore synchronisierten Entwuerfen, siehe apicore-sync.ts). */ export async function toggleHarvestPublishedAction(formData: FormData): Promise { const admin = await requireAdmin(); const id = str(formData, "id"); const isPublished = formData.get("isPublished") === "on"; const harvest = await prisma.harvest.update({ where: { id }, data: { isPublished }, }); await logAdminAction(admin, { action: isPublished ? "harvest.publish" : "harvest.unpublish", entityType: "Harvest", entityId: id, detail: `${harvest.type} ${harvest.year}`, }); revalidatePath("/admin/ernten"); revalidatePath("/ernten"); redirect("/admin/ernten"); } export async function createBatchAction(formData: FormData): Promise { const admin = await requireAdmin(); const code = str(formData, "code"); const batch = await prisma.batch.create({ data: { code, harvestId: str(formData, "harvestId"), fillDate: new Date(str(formData, "fillDate")), bestBeforeDate: new Date(str(formData, "bestBeforeDate")), glassCount: num(formData, "glassCount"), remainingGlasses: num(formData, "remainingGlasses"), images: strArray(formData, "images").filter(isValidUploadPath), }, }); await logAdminAction(admin, { action: "batch.create", entityType: "Batch", entityId: batch.id, detail: code }); revalidatePath("/admin/chargen"); redirect("/admin/chargen"); } export async function updateBatchAction(formData: FormData): Promise { const admin = await requireAdmin(); const id = str(formData, "id"); await prisma.batch.update({ where: { id }, data: { fillDate: new Date(str(formData, "fillDate")), bestBeforeDate: new Date(str(formData, "bestBeforeDate")), glassCount: num(formData, "glassCount"), remainingGlasses: num(formData, "remainingGlasses"), images: strArray(formData, "images").filter(isValidUploadPath), }, }); await logAdminAction(admin, { action: "batch.update", entityType: "Batch", entityId: id }); revalidatePath("/admin/chargen"); redirect("/admin/chargen"); } /** * Loescht eine Charge unwiderruflich. productId/batchId auf OrderItem sind * onDelete: Restrict (siehe prisma/schema.prisma) — eine Charge, die schon * in mindestens einer Bestellposition verwendet wurde, darf aus * Rechnungs-/Nachweispflicht NIE geloescht werden (Chargen-Code steht auf * dem QR-Code des Glases und in Rechnungen). Batch.products ist ebenfalls * Restrict. Die Zaehlung hier VOR dem Loeschversuch liefert dafuer eine * konkrete, verstaendliche Fehlermeldung statt eines rohen Datenbankfehlers. */ export async function deleteBatchAction(formData: FormData): Promise { const admin = await requireAdmin(); const id = str(formData, "id"); const batch = await prisma.batch.findUnique({ where: { id }, include: { _count: { select: { products: true, orderItems: true } } }, }); if (!batch) fail("/admin/chargen", "Charge nicht gefunden."); if (batch._count.orderItems > 0) { fail( "/admin/chargen", `Charge "${batch.code}" wurde bereits in ${batch._count.orderItems} Bestellposition(en) verwendet und kann deshalb nicht gelöscht werden.` ); } if (batch._count.products > 0) { fail( "/admin/chargen", `Charge "${batch.code}" ist noch ${batch._count.products} Produkt(en) zugeordnet. Löse diese Zuordnung zuerst.` ); } await prisma.batch.delete({ where: { id } }); await logAdminAction(admin, { action: "batch.delete", entityType: "Batch", entityId: id, detail: batch.code }); revalidatePath("/admin/chargen"); redirect("/admin/chargen"); }