import { FastifyInstance } from "fastify"; import prisma from "../../config/database"; import { requirePermission } from "../../middleware/auth"; import { localDateCzStr } from "../../utils/date"; import { nasOffersManager } from "../../services/nas-offers-manager"; import { htmlToPdf } from "../../utils/html-to-pdf"; function formatDate(date: Date | string | null | undefined): string { if (!date) return ""; const d = new Date(date); if (isNaN(d.getTime())) return String(date); return localDateCzStr(d); } /** Format number with comma decimal separator and non-breaking space thousands separator */ function formatNum(n: number, decimals: number): string { const abs = Math.abs(n); const fixed = abs.toFixed(decimals); const [intPart, decPart] = fixed.split("."); const withSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, "\u00A0"); const result = decPart ? `${withSep},${decPart}` : withSep; return n < 0 ? `-${result}` : result; } function formatCurrency(amount: number, currency: string): string { const n = Number(amount) || 0; switch (currency) { case "EUR": return `${formatNum(n, 2)} \u20AC`; case "USD": return `$${Math.abs(n) .toFixed(2) .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`; case "CZK": return `${formatNum(n, 2)} K\u010D`; case "GBP": return `\u00A3${Math.abs(n) .toFixed(2) .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`; default: return `${formatNum(n, 2)} ${currency}`; } } function escapeHtml(str: string | null | undefined): string { if (!str) return ""; return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } /** Sanitize Quill HTML: keep safe tags, remove event handlers, merge adjacent spans */ function cleanQuillHtml(html: string | null | undefined): string { if (!html) return ""; const allowedTags = "