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" interface ProductRecord { name: string sku: string ean: string qtyOnHand: number locatorX?: string locatorY?: string locatorZ?: string locatorPriority?: number locatorTypeName?: string } const formatDate = (date: Date, lang: string) => { const isGerman = lang.startsWith('de') return date.toLocaleDateString(isGerman ? 'de-DE' : 'en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) } const generatePdfReport = async (records: ProductRecord[], organizationName: string, lang: string, showByLocator: boolean = false) => { const isGerman = lang.startsWith('de') const pdfDoc = await PDFDocument.create() const font = await pdfDoc.embedFont(StandardFonts.Helvetica) const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold) // Use landscape for locator view to fit more columns const pageWidth = showByLocator ? 841.89 : 595.28 // A4 landscape or portrait const pageHeight = showByLocator ? 595.28 : 841.89 const margin = 40 const lineHeight = 16 const headerHeight = 22 // Column widths based on view mode const colWidths = showByLocator ? { name: 150, sku: 80, ean: 100, qty: 50, x: 50, y: 50, z: 50, priority: 55, type: 90 } : { name: 200, sku: 100, ean: 120, qty: 75 } const addNewPage = () => { const page = pdfDoc.addPage([pageWidth, pageHeight]) return page } const drawHeader = (page: any, yPos: number) => { const headers = showByLocator ? (isGerman ? ['Produktname', 'SKU', 'EAN', 'Menge', 'X', 'Y', 'Z', 'Prioritaet', 'Lagertyp'] : ['Product Name', 'SKU', 'EAN', 'Qty', 'X', 'Y', 'Z', 'Priority', 'Locator Type']) : (isGerman ? ['Produktname', 'SKU', 'EAN', 'Bestand'] : ['Product Name', 'SKU', 'EAN', 'Qty On Hand']) let xPos = margin // Header background page.drawRectangle({ x: margin - 5, y: yPos - 5, width: pageWidth - (margin * 2) + 10, height: headerHeight, color: rgb(0.9, 0.9, 0.9) }) // Header text const fontSize = showByLocator ? 8 : 10 page.drawText(headers[0], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.name page.drawText(headers[1], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.sku page.drawText(headers[2], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.ean page.drawText(headers[3], { x: xPos, y: yPos, size: fontSize, font: fontBold }) if (showByLocator) { xPos += colWidths.qty page.drawText(headers[4], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.x page.drawText(headers[5], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.y page.drawText(headers[6], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.z page.drawText(headers[7], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.priority page.drawText(headers[8], { x: xPos, y: yPos, size: fontSize, font: fontBold }) } return yPos - headerHeight - 5 } const drawRow = (page: any, record: ProductRecord, yPos: number) => { let xPos = margin const fontSize = showByLocator ? 7 : 9 const maxNameLength = showByLocator ? 25 : 35 // Truncate name if too long let displayName = record.name || '' if (displayName.length > maxNameLength) { displayName = displayName.substring(0, maxNameLength - 3) + '...' } page.drawText(displayName, { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.name page.drawText(record.sku || '', { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.sku page.drawText(record.ean || '', { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.ean page.drawText(String(record.qtyOnHand || 0), { x: xPos, y: yPos, size: fontSize, font }) if (showByLocator) { xPos += colWidths.qty page.drawText(record.locatorX || '', { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.x page.drawText(record.locatorY || '', { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.y page.drawText(record.locatorZ || '', { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.z page.drawText(String(record.locatorPriority ?? ''), { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.priority let locatorType = record.locatorTypeName || '' if (locatorType.length > 15) { locatorType = locatorType.substring(0, 12) + '...' } page.drawText(locatorType, { x: xPos, y: yPos, size: fontSize, font }) } return yPos - lineHeight } let page = addNewPage() let yPos = pageHeight - margin // Title const title = isGerman ? `Bestandsbericht - ${organizationName}` : `Stock Report - ${organizationName}` page.drawText(title, { x: margin, y: yPos, size: 16, font: fontBold }) yPos -= 25 // Date const dateLabel = isGerman ? 'Erstellt am: ' : 'Generated on: ' page.drawText(dateLabel + formatDate(new Date(), lang), { x: margin, y: yPos, size: 10, font }) yPos -= 15 // Record count const countLabel = isGerman ? 'Anzahl Eintraege: ' : 'Total Records: ' page.drawText(countLabel + records.length, { x: margin, y: yPos, size: 10, font }) // View mode indicator if (showByLocator) { const viewLabel = isGerman ? 'Ansicht: Nach Lagerplatz' : 'View: By Locator' page.drawText(viewLabel, { x: margin + 150, y: yPos, size: 10, font }) } yPos -= 30 // Draw table header yPos = drawHeader(page, yPos) // Draw rows for (const record of records) { if (yPos < margin + 40) { // Add new page page = addNewPage() yPos = pageHeight - margin yPos = drawHeader(page, yPos) } yPos = drawRow(page, record, yPos) } // Summary at the bottom yPos -= 20 if (yPos < margin + 60) { page = addNewPage() yPos = pageHeight - margin } const totalQty = records.reduce((sum, r) => sum + (r.qtyOnHand || 0), 0) const summaryLabel = isGerman ? 'Gesamtbestand: ' : 'Total Stock: ' page.drawText(summaryLabel + totalQty, { x: margin, y: yPos, size: 12, font: fontBold }) const pdfBytes = await pdfDoc.save() return Buffer.from(pdfBytes) } const generateHtmlEmailDE = (organizationName: string, recordCount: number, totalQty: number) => { return `