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 TransactionRecord { id: string | number movementDate: string movementQty: number movementType: string movementTypeId: string locator: string bpartnerName: string externalOrderId: 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 formatMovementDate = (dateStr: string, lang: string) => { if (!dateStr) return '' const isGerman = lang.startsWith('de') const date = new Date(dateStr) return date.toLocaleDateString(isGerman ? 'de-DE' : 'en-US') } const getMovementTypeInfo = (movementTypeId: string) => { const isPositive = movementTypeId?.endsWith('+') return { label: isPositive ? 'IN' : 'OUT', color: isPositive ? rgb(0.15, 0.68, 0.38) : rgb(0.91, 0.30, 0.24) } } const generateCsvReport = (records: TransactionRecord[], productName: string, lang: string) => { const isGerman = lang.startsWith('de') const headers = isGerman ? ['Nr.', 'Typ', 'Datum', 'Menge', 'Externe Bestellnummer', 'Geschaeftspartner', 'Lagerort', 'Bewegungsart'] : ['No.', 'Type', 'Date', 'Quantity', 'External Order ID', 'Business Partner', 'Locator', 'Movement Type'] const rows = records.map((r, index) => { const typeInfo = getMovementTypeInfo(r.movementTypeId) const qty = r.movementQty || 0 const qtyStr = qty > 0 ? `+${qty}` : `${qty}` return [ index + 1, typeInfo.label, formatMovementDate(r.movementDate, lang), qtyStr, r.externalOrderId || '', r.bpartnerName || '', r.locator || '', r.movementType || '' ] }) const csvContent = [ headers.join(';'), ...rows.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(';')) ].join('\n') return Buffer.from('\uFEFF' + csvContent, 'utf-8') } const generatePdfReport = async (records: TransactionRecord[], productName: string, productSku: 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) // Use landscape for better fit const pageWidth = 841.89 // A4 landscape const pageHeight = 595.28 const margin = 40 const lineHeight = 14 const headerHeight = 20 const colWidths = { no: 30, type: 35, date: 65, qty: 45, extOrder: 95, bpartner: 170, locator: 75, movType: 110 } const addNewPage = () => { return pdfDoc.addPage([pageWidth, pageHeight]) } const drawHeader = (page: any, yPos: number) => { const headers = isGerman ? ['Nr.', 'Typ', 'Datum', 'Menge', 'Ext. Bestellnr.', 'Geschaeftspartner', 'Lagerort', 'Bewegungsart'] : ['No.', 'Type', 'Date', 'Qty', 'External Order', 'Business Partner', 'Locator', 'Movement Type'] 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) }) const fontSize = 8 page.drawText(headers[0], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.no page.drawText(headers[1], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.type page.drawText(headers[2], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.date page.drawText(headers[3], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.qty page.drawText(headers[4], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.extOrder page.drawText(headers[5], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.bpartner page.drawText(headers[6], { x: xPos, y: yPos, size: fontSize, font: fontBold }) xPos += colWidths.locator page.drawText(headers[7], { x: xPos, y: yPos, size: fontSize, font: fontBold }) return yPos - headerHeight - 5 } const drawRow = (page: any, record: TransactionRecord, yPos: number, rowNumber: number) => { let xPos = margin const fontSize = 7 const typeInfo = getMovementTypeInfo(record.movementTypeId) const qty = record.movementQty || 0 const qtyStr = qty > 0 ? `+${qty}` : `${qty}` // Row number page.drawText(String(rowNumber), { x: xPos, y: yPos, size: fontSize, font, color: rgb(0.4, 0.4, 0.4) }) xPos += colWidths.no // Type with color page.drawText(typeInfo.label, { x: xPos, y: yPos, size: fontSize, font: fontBold, color: typeInfo.color }) xPos += colWidths.type // Date page.drawText(formatMovementDate(record.movementDate, lang), { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.date // Quantity with color page.drawText(qtyStr, { x: xPos, y: yPos, size: fontSize, font: fontBold, color: typeInfo.color }) xPos += colWidths.qty // External Order ID const extOrder = (record.externalOrderId || '').substring(0, 15) page.drawText(extOrder, { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.extOrder // Business Partner (truncate if needed) let bpartner = record.bpartnerName || '' if (bpartner.length > 35) bpartner = bpartner.substring(0, 32) + '...' page.drawText(bpartner, { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.bpartner // Locator const locator = (record.locator || '').substring(0, 12) page.drawText(locator, { x: xPos, y: yPos, size: fontSize, font }) xPos += colWidths.locator // Movement Type const movType = (record.movementType || '').substring(0, 20) page.drawText(movType, { x: xPos, y: yPos, size: fontSize, font }) return yPos - lineHeight } let page = addNewPage() let yPos = pageHeight - margin // Title const titleLabel = isGerman ? 'Produkthistorie: ' : 'Product History: ' page.drawText(titleLabel + productName, { x: margin, y: yPos, size: 14, font: fontBold }) yPos -= 20 // SKU if present if (productSku) { page.drawText(`SKU: ${productSku}`, { x: margin, y: yPos, size: 10, font }) yPos -= 15 } // Date and count const dateLabel = isGerman ? 'Erstellt am: ' : 'Generated on: ' page.drawText(dateLabel + formatDate(new Date(), lang), { x: margin, y: yPos, size: 10, font }) yPos -= 15 const countLabel = isGerman ? 'Anzahl Transaktionen: ' : 'Total Transactions: ' page.drawText(countLabel + records.length, { x: margin, y: yPos, size: 10, font }) yPos -= 25 // Draw table header yPos = drawHeader(page, yPos) // Draw rows let rowNumber = 0 for (const record of records) { if (yPos < margin + 50) { page = addNewPage() yPos = pageHeight - margin yPos = drawHeader(page, yPos) } rowNumber++ yPos = drawRow(page, record, yPos, rowNumber) } // Summary at the bottom yPos -= 20 if (yPos < margin + 60) { page = addNewPage() yPos = pageHeight - margin } const totalIn = records.filter(r => (r.movementQty || 0) > 0).reduce((sum, r) => sum + (r.movementQty || 0), 0) const totalOut = records.filter(r => (r.movementQty || 0) < 0).reduce((sum, r) => sum + Math.abs(r.movementQty || 0), 0) const inLabel = isGerman ? 'Gesamt Eingang: +' : 'Total IN: +' const outLabel = isGerman ? 'Gesamt Ausgang: -' : 'Total OUT: -' page.drawText(inLabel + totalIn, { x: margin, y: yPos, size: 10, font: fontBold, color: rgb(0.15, 0.68, 0.38) }) page.drawText(outLabel + totalOut, { x: margin + 150, y: yPos, size: 10, font: fontBold, color: rgb(0.91, 0.30, 0.24) }) const pdfBytes = await pdfDoc.save() return Buffer.from(pdfBytes) } const generateHtmlEmailDE = (productName: string, productSku: string, recordCount: number, totalIn: number, totalOut: number) => { return `