import { composeCertificateText, type CertificateText } from "@/lib/certificate/text"; import { layoutCertificate } from "@/lib/certificate/layout"; import { CURRENT_TEMPLATE_VERSION } from "@/lib/certificate/templates"; import { renderCertificatePdf } from "@/lib/certificate/render-pdf"; import { renderCertificateImage, type WatermarkOption } from "@/lib/certificate/render-image"; import { fixedTermFor, personalizationSchema, type CertificateTerm, type Personalization } from "@/lib/certificate/schema"; import type { Locale } from "@/i18n/config"; /** * Bruecke zwischen Patenschaft (Datenbank) und Urkunden-Engine * (lib/certificate). Hier und nur hier wird entschieden, mit welchen Daten * eine Urkunde gerendert wird — Ausstellung, Korrektur, Vorschau und die * 3D-Textur rufen alle diese Funktionen auf. Dadurch koennen Vorschau und * gespeicherte PDF nicht auseinanderlaufen: gleiche Eingabe, gleicher Weg. */ /** Was an der Bestellposition bzw. am Abo als Personalisierung gespeichert wird. */ export interface StoredPersonalization { personalization: Personalization; templateVersion: string; } export interface CertificateDocumentInput { personalization: Personalization; locale: Locale; term: CertificateTerm; number: string; templateVersion: string; } /** Bei Vorschauen vor dem Kauf gibt es noch keine Nummer; sichtbar ist dieser Platzhalter. */ export const PREVIEW_NUMBER = "WH-P-0000-00000"; /** * Liest eine gespeicherte Personalisierung und prueft sie erneut gegen das * Schema. Was in der Datenbank steht, ist beim Kauf geprueft worden — eine * Schemaaenderung seitdem soll aber laut auffallen statt still eine Urkunde * mit unerwarteten Daten zu erzeugen. */ export function parseStoredPersonalization(value: unknown): StoredPersonalization { if (!value || typeof value !== "object") { throw new Error("Keine Personalisierung gespeichert."); } const raw = value as { personalization?: unknown; templateVersion?: unknown }; const parsed = personalizationSchema.safeParse(raw.personalization); if (!parsed.success) { throw new Error(`Gespeicherte Personalisierung ungueltig: ${parsed.error.issues[0]?.message ?? "unbekannt"}`); } return { personalization: parsed.data, templateVersion: typeof raw.templateVersion === "string" ? raw.templateVersion : CURRENT_TEMPLATE_VERSION, }; } export function toStoredPersonalization(personalization: Personalization, templateVersion = CURRENT_TEMPLATE_VERSION): StoredPersonalization { return { personalization, templateVersion }; } /** Laufzeitform einer Patenschaft aus ihren Daten (YYYY-MM-DD). */ export function termForSponsorship(input: { startDate: Date; endDate: Date | null; isSubscription: boolean }): CertificateTerm { const start = toIsoDate(input.startDate); if (!input.isSubscription) { return input.endDate ? { kind: "FIXED", start, end: toIsoDate(input.endDate) } : fixedTermFor(start); } return input.endDate ? { kind: "ENDED", start, end: toIsoDate(input.endDate) } : { kind: "OPEN", start }; } export function composeDocumentText(input: CertificateDocumentInput): CertificateText { return composeCertificateText({ personalization: input.personalization, locale: input.locale, term: input.term, number: input.number, }); } export async function renderCertificateDocumentPdf( input: CertificateDocumentInput, creationDate: Date ): Promise<{ pdf: Buffer; text: CertificateText }> { const text = composeDocumentText(input); const layout = layoutCertificate(text, input.templateVersion); const pdf = await renderCertificatePdf(layout, { title: `Bienenpatenschaft ${text.beeName}`, logo: null, creationDate, }); return { pdf, text }; } export async function renderCertificateDocumentImage( input: CertificateDocumentInput, options: { widthPx: number; watermark?: WatermarkOption; format: "webp" | "png" } ): Promise { const layout = layoutCertificate(composeDocumentText(input), input.templateVersion); return renderCertificateImage(layout, options); } /** * Wasserzeichen der Vorschau. Die Engine setzt den Wortlaut je Sprache selbst * (render-image.ts, WATERMARK_TEXT) — hier wird nur die Sprache gewaehlt. */ export function previewWatermark(locale: Locale): Locale { return locale; } export function toIsoDate(date: Date): string { return date.toISOString().slice(0, 10); } export function fromIsoDate(value: string): Date { return new Date(`${value}T00:00:00.000Z`); }