feat: Ernten und Chargen im Admin loeschbar machen
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m27s

Neue deleteHarvestAction/deleteBatchAction, jeweils mit vorheriger
Referenzpruefung statt rohem Datenbankfehler: eine Ernte laesst sich nur
loeschen, wenn keine Chargen/Produkte mehr darauf verweisen, eine Charge
nur, wenn sie keinem Produkt zugeordnet ist und in keiner Bestellung
verwendet wurde (Chargen-Code steht auf Rechnungen/QR-Codes und darf aus
Nachweispflicht nie verschwinden). Loeschen-Button erscheint in den
Listen nur, wenn die Loeschung tatsaechlich moeglich ist, mit
Bestaetigungsdialog wie beim bestehenden Benutzer-Loeschen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-11 23:45:23 +02:00
1 parent c7286041a1
commit a3b11cc8e2
6 files changed
+138 -9

No files matched your search

+80 -1
View File
@@ -5,7 +5,7 @@ import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
import { logAdminAction } from "@/lib/audit-log";
import { isValidUploadPath } from "@/lib/uploads";
import { requireAdmin, str, num, strArray } from "./_shared";
import { requireAdmin, fail, str, num, strArray } from "./_shared";
export async function createHarvestAction(formData: FormData): Promise<void> {
const admin = await requireAdmin();
@@ -68,6 +68,45 @@ export async function updateHarvestAction(formData: FormData): Promise<void> {
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<void> {
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
@@ -136,3 +175,43 @@ export async function updateBatchAction(formData: FormData): Promise<void> {
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<void> {
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");
}