import nodemailer from 'nodemailer' import { PDFDocument, StandardFonts, rgb } from 'pdf-lib' import refreshTokenHelper from "../../utils/refreshTokenHelper" import forceLogoutHelper from "../../utils/forceLogoutHelper" import errorHandlingHelper from "../../utils/errorHandlingHelper" const formatCurrency = (value: number) => { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value || 0) } const formatPct = (value: number) => { return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 1 }).format(value || 0) + '%' } const formatDateStr = (dateStr: string) => { if (!dateStr) return '' return new Date(dateStr).toLocaleDateString('de-DE') } const generatePdfReport = async (records: any[], summary: any, orgName: string, dateRange: string, lang: string) => { const isGerman = lang.startsWith('de') const pdfDoc = await PDFDocument.create() const font = await pdfDoc.embedFont(StandardFonts.Helvetica) const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold) const pageWidth = 841.89 // A4 landscape const pageHeight = 595.28 const margin = 14 const addNewPage = () => pdfDoc.addPage([pageWidth, pageHeight]) let page = addNewPage() let yPos = pageHeight - 25 // Title const title = isGerman ? `Margenanalyse: ${orgName}` : `Margin Analysis: ${orgName}` page.drawText(title, { x: margin, y: yPos, size: 16, font: fontBold }) yPos -= 14 if (dateRange) { page.drawText(`${isGerman ? 'Zeitraum' : 'Period'}: ${dateRange}`, { x: margin, y: yPos, size: 10, font }) yPos -= 10 } const now = new Date() page.drawText(`${isGerman ? 'Erstellt' : 'Generated'}: ${now.toLocaleDateString('de-DE')} ${now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`, { x: margin, y: yPos, size: 9, font, color: rgb(0.4, 0.4, 0.4) }) yPos -= 18 // Summary cards const cardW = 120 const cardH = 32 const cardGap = 10 const drawCard = (x: number, value: string, label: string, bgColor: { r: number, g: number, b: number }) => { page.drawRectangle({ x, y: yPos - cardH, width: cardW, height: cardH, color: rgb(bgColor.r, bgColor.g, bgColor.b), borderColor: rgb(bgColor.r, bgColor.g, bgColor.b), borderWidth: 0 }) // Round corners simulated by just using rectangle page.drawText(value, { x: x + 6, y: yPos - 13, size: 11, font: fontBold, color: rgb(1, 1, 1) }) page.drawText(label, { x: x + 6, y: yPos - 26, size: 7, font, color: rgb(1, 1, 1) }) } const orders = summary.uniqueOrders || 0 const lines = summary.recordCount || records.length const grandTotal = summary.totalGrandTotal || 0 const salesNet = summary.totalSalesNet || 0 const totalShipping = summary.totalShipping || 0 const totalFees = summary.totalReferralFees || 0 const totalMargin = summary.totalMargin || 0 const marginPct = summary.avgMarginPct || 0 drawCard(margin, `${orders} (${lines} lines)`, isGerman ? 'Bestellungen' : 'Orders', { r: 0.196, g: 0.451, b: 0.863 }) drawCard(margin + cardW + cardGap, formatCurrency(grandTotal), isGerman ? 'Gesamtbetrag (brutto)' : 'Grand Total (gross)', { r: 0.125, g: 0.612, b: 0.933 }) drawCard(margin + 2 * (cardW + cardGap), formatCurrency(salesNet), isGerman ? 'Umsatz (netto)' : 'Sales Net', { r: 0.3, g: 0.3, b: 0.3 }) drawCard(margin + 3 * (cardW + cardGap), formatCurrency(totalShipping), isGerman ? 'Versandkosten' : 'Shipping Cost', { r: 0.906, g: 0.298, b: 0.235 }) drawCard(margin + 4 * (cardW + cardGap), formatCurrency(totalFees), isGerman ? 'Gebuehren' : 'Referral Fees', { r: 1, g: 0.651, b: 0 }) // Margin card - green if positive, red if negative const marginColor = totalMargin >= 0 ? { r: 0.153, g: 0.682, b: 0.376 } : { r: 0.906, g: 0.298, b: 0.235 } drawCard(margin + 5 * (cardW + cardGap), `${formatCurrency(totalMargin)} (${formatPct(marginPct)})`, isGerman ? 'Gesamtmarge' : 'Total Margin', marginColor) yPos -= cardH + 14 // Table headers const colX = [14, 80, 140, 230, 315, 345, 395, 445, 495, 535, 585, 640, 690, 740, 790] const headers = ['Order', 'Date', isGerman ? 'Partner' : 'Partner', 'Product', 'Qty', isGerman ? 'Preis(n)' : 'Price(n)', isGerman ? 'Preis(b)' : 'Price(g)', 'Tax%', 'Line Net', isGerman ? 'Einkauf' : 'Purch.', isGerman ? 'Versand' : 'Ship.', 'Ref%', 'Ref.Fee', 'Margin', 'M%'] const drawTableHeader = (p: any, y: number) => { p.drawRectangle({ x: margin, y: y - 3, width: pageWidth - 2 * margin, height: 12, color: rgb(0.94, 0.95, 0.96) }) headers.forEach((h, i) => { p.drawText(h, { x: colX[i], y: y, size: 6.5, font: fontBold, color: rgb(0.2, 0.2, 0.2) }) }) return y - 14 } yPos = drawTableHeader(page, yPos) // Table rows const bottomMargin = 30 for (const row of records) { if (yPos < bottomMargin) { page = addNewPage() yPos = pageHeight - 20 yPos = drawTableHeader(page, yPos) } const fs = 6 const marginVal = row.margin || 0 const marginClr = marginVal > 0 ? rgb(0.18, 0.49, 0.2) : marginVal < 0 ? rgb(0.78, 0.16, 0.16) : rgb(0, 0, 0) page.drawText(String(row.documentNo || ''), { x: colX[0], y: yPos, size: fs, font }) page.drawText(formatDateStr(row.dateOrdered), { x: colX[1], y: yPos, size: fs, font }) let partner = row.partner || '' if (partner.length > 22) partner = partner.substring(0, 19) + '...' page.drawText(partner, { x: colX[2], y: yPos, size: fs, font }) let product = row.product || '' if (product.length > 22) product = product.substring(0, 19) + '...' page.drawText(product, { x: colX[3], y: yPos, size: fs, font }) page.drawText(String(row.qty || 0), { x: colX[4], y: yPos, size: fs, font }) page.drawText(formatCurrency(row.salesPriceNet || 0), { x: colX[5], y: yPos, size: fs, font }) page.drawText(formatCurrency(row.salesPriceGross || 0), { x: colX[6], y: yPos, size: fs, font }) page.drawText((row.taxRate || 0) + '%', { x: colX[7], y: yPos, size: fs, font }) page.drawText(formatCurrency(row.lineNetAmt || 0), { x: colX[8], y: yPos, size: fs, font: fontBold }) page.drawText(formatCurrency(row.purchasePrice || 0), { x: colX[9], y: yPos, size: fs, font }) page.drawText(formatCurrency(row.lineShippingCost || 0), { x: colX[10], y: yPos, size: fs, font, color: rgb(0.9, 0.22, 0.15) }) page.drawText(row.referralPct ? row.referralPct + '%' : '', { x: colX[11], y: yPos, size: fs, font }) page.drawText(formatCurrency(row.referralFee || 0), { x: colX[12], y: yPos, size: fs, font, color: row.referralFee > 0 ? rgb(0.9, 0.22, 0.15) : rgb(0, 0, 0) }) page.drawText(formatCurrency(marginVal), { x: colX[13], y: yPos, size: fs, font: fontBold, color: marginClr }) page.drawText(formatPct(row.marginPct || 0), { x: colX[14], y: yPos, size: fs, font, color: marginClr }) yPos -= 9 } return Buffer.from(await pdfDoc.save()) } const generateHtmlEmail = (orgName: string, dateRange: string, summary: any, lang: string) => { const isGerman = lang.startsWith('de') const title = isGerman ? 'Margenanalyse' : 'Margin Analysis' return `