import ExcelJS from "exceljs"; import { NextResponse } from "next/server"; import { requireHrUser } from "@/lib/auth/require-hr"; import { exportFilename, exportResponseHeaders } from "@/lib/export"; import { BLAETTER } from "@/lib/import/schema"; // Die Vorlage entsteht aus demselben Schema wie die Prüfung. // // Das ist der Punkt: eine von Hand gepflegte Beispieldatei läuft dem Code // hinterher, und dann verlangt die Vorlage eine Spalte, die es nicht mehr // gibt — oder umgekehrt. Hier kann das nicht passieren; kommt in schema.ts // eine Spalte dazu, steht sie beim nächsten Herunterladen drin. export async function GET() { const gate = await requireHrUser(); if ("denied" in gate) return gate.denied; const mappe = new ExcelJS.Workbook(); mappe.creator = "Alpenwerk HR"; mappe.created = new Date(); const hinweise = mappe.addWorksheet("Hinweise"); hinweise.columns = [ { header: "Blatt", width: 16 }, { header: "Spalte", width: 26 }, { header: "Pflicht", width: 9 }, { header: "Format", width: 30 }, { header: "Hinweis", width: 70 }, ]; hinweise.getRow(1).font = { bold: true }; const formatText = (typ: (typeof BLAETTER)[number]["spalten"][number]["typ"]): string => { switch (typ.art) { case "datum": return "Datum (31.12.2026)"; case "zahl": return "Zahl (38,5)"; case "ganzzahl": return "Ganze Zahl"; case "janein": return "ja / nein"; case "liste": return typ.werte ? `Mehrere mit Semikolon aus: ${typ.werte.join(", ")}` : "Mehrere mit Semikolon"; case "auswahl": return typ.werte.join(" | "); default: return "Text"; } }; for (const schema of BLAETTER) { hinweise.addRow([schema.name, "", "", "", schema.zweck]).font = { bold: true }; for (const s of schema.spalten) { hinweise.addRow([schema.name, s.name, s.pflicht ? "ja" : "", formatText(s.typ), s.hinweis]); } hinweise.addRow([]); } for (const schema of BLAETTER) { const blatt = mappe.addWorksheet(schema.name); blatt.columns = schema.spalten.map((s) => ({ header: s.name, width: Math.max(12, Math.min(28, s.name.length + 4)), })); const kopf = blatt.getRow(1); kopf.font = { bold: true }; kopf.eachCell((zelle, i) => { const spalte = schema.spalten[i - 1]; if (!spalte) return; // Pflichtspalten sichtbar markieren — sonst ist die erste Rückmeldung // eine Fehlerliste statt eines Hinweises beim Ausfüllen. if (spalte.pflicht) { zelle.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFFDE7EF" } }; } const teile = [spalte.pflicht ? "Pflichtfeld." : "Optional.", formatText(spalte.typ), spalte.hinweis].filter(Boolean); zelle.note = teile.join("\n"); }); // Eine Beispielzeile. Alles als Text, damit Excel nicht selbst // interpretiert — der Leser deutet die Werte ohnehin. const beispiel = schema.spalten.map((s) => s.beispiel); if (beispiel.some(Boolean)) { const zeile = blatt.addRow(beispiel); zeile.font = { italic: true, color: { argb: "FF8A8A8A" } }; zeile.eachCell((z) => { z.numFmt = "@"; }); } blatt.views = [{ state: "frozen", ySplit: 1 }]; } const puffer = await mappe.xlsx.writeBuffer(); const dateiname = exportFilename("import-vorlage", "xlsx"); return new NextResponse(new Blob([puffer as BlobPart]), { headers: exportResponseHeaders(dateiname, "xlsx") }); }