"use server"; import { redirect } from "next/navigation"; import { revalidatePath } from "next/cache"; import { prisma } from "@/lib/prisma"; import { logAdminAction } from "@/lib/audit-log"; import { fetchApiCoreHarvests, ApiCoreConfigError, ApiCoreRequestError, type ApiCoreHarvestWithApiaryName, } from "@/lib/apicore-client"; import { requireAdmin } from "./_shared"; /** * Stabiler, eindeutiger Slug aus der ApiCore-Ernte-ID statt aus Typ/Jahr * abgeleitet (wie beim manuellen createHarvestAction) — dient hier zugleich * als Idempotenz-Schluessel: ein wiederholter Sync erkennt an diesem Slug, * ob eine ApiCore-Ernte schon einmal importiert wurde, statt bei jedem Lauf * Duplikate anzulegen. */ function slugFor(sourceId: string): string { return `apicore-${sourceId}`; } /** * Wabenhains Harvest.description ist ein Pflichtfeld, ApiCore liefert dafuer * kein Aequivalent — baut stattdessen eine sinnvolle Vorbefuellung aus den * vorhandenen Mengen-/Notizfeldern, die der Admin beim Review verfeinert. */ function buildDescription(source: ApiCoreHarvestWithApiaryName): string { const parts: string[] = []; if (source.honeySuperCount != null) parts.push(`${source.honeySuperCount} Honigräume`); if (source.grossWeight != null) parts.push(`Bruttogewicht ${source.grossWeight} kg`); if (source.netWeight != null) parts.push(`Nettogewicht ${source.netWeight} kg`); if (source.notes?.trim()) parts.push(source.notes.trim()); const detail = parts.length > 0 ? parts.join(" · ") : "Bitte Beschreibung ergänzen."; return `Automatisch aus ApiCore importiert. ${detail}`; } /** * Zieht Ernten aus ApiCore und legt sie als unveroeffentlichte Entwuerfe an * (isPublished: false) bzw. aktualisiert bereits vorhandene, noch * unveroeffentlichte Entwuerfe. Bereits vom Admin veroeffentlichte, * frueher synchronisierte Ernten werden NICHT angefasst — sonst wuerde ein * erneuter Sync eine bewusste Freigabe- und Bearbeitungsentscheidung des * Admins stillschweigend zuruecksetzen. * * Admin-Berechtigungsfeld "type" bei Wabenhain (Erntesaison, z.B. * "Sommertracht") und ApiCores "honeyType" (Honigsorte, z.B. "Lindenhonig") * sind KEINE aequivalenten Taxonomien — es findet bewusst keine automatische * Uebersetzung statt, der Rohwert wird uebernommen und muss vom Admin beim * Review korrigiert werden. */ export async function syncHarvestsFromApiCoreAction(): Promise { const admin = await requireAdmin(); let sourceHarvests: ApiCoreHarvestWithApiaryName[]; try { sourceHarvests = await fetchApiCoreHarvests(); } catch (error: unknown) { const message = error instanceof ApiCoreConfigError || error instanceof ApiCoreRequestError ? error.message : error instanceof Error ? error.message : "Unbekannter Fehler"; redirect(`/admin/ernten?syncError=${encodeURIComponent(message)}`); } let created = 0; let updated = 0; let skipped = 0; let failed = 0; for (const source of sourceHarvests) { const slug = slugFor(source.id); const date = new Date(source.date); if (Number.isNaN(date.getTime())) { failed++; console.error(`[apicore-sync] Ernte ${source.id} hat ein ungueltiges Datum ("${source.date}") — übersprungen.`); continue; } const type = source.honeyType?.trim() || `Ernte ${source.id.slice(0, 8)}`; try { const existing = await prisma.harvest.findUnique({ where: { slug } }); if (existing?.isPublished) { skipped++; continue; } const data = { year: date.getFullYear(), type, harvestDate: date, location: source.apiaryName ?? "Altenmünster", description: buildDescription(source), }; if (existing) { await prisma.harvest.update({ where: { slug }, data }); updated++; } else { await prisma.harvest.create({ data: { ...data, slug, isPublished: false } }); created++; } } catch (error: unknown) { failed++; const message = error instanceof Error ? error.message : "Unbekannter Fehler"; // z.B. P2002 (unique constraint year+type), falls zwei ApiCore-Ernten // im selben Jahr denselben honeyType tragen — bewusst nicht fatal fuer // den gesamten Sync-Lauf, siehe Kommentar oben zur Taxonomie-Kollision. console.error(`[apicore-sync] Ernte ${source.id} konnte nicht importiert werden: ${message}`); } } await logAdminAction(admin, { action: "harvest.sync_apicore", entityType: "Harvest", detail: `${created} neu, ${updated} aktualisiert, ${skipped} übersprungen, ${failed} fehlgeschlagen`, }); revalidatePath("/admin/ernten"); redirect(`/admin/ernten?synced=${created}&updated=${updated}&skipped=${skipped}&failed=${failed}`); }