Spaces:
Sleeping
Sleeping
| import PDFDocument from 'pdfkit'; | |
| import type { InteracTransaction } from '@icc/shared'; | |
| const ICC_BLUE = '#1773cf'; | |
| const GRAY_TEXT = '#4B5563'; | |
| const LIGHT_GRAY = '#F3F4F6'; | |
| function formatCurrencyPdf(amount: number): string { | |
| return new Intl.NumberFormat('fr-CA', { | |
| style: 'currency', | |
| currency: 'CAD', | |
| minimumFractionDigits: 2, | |
| }).format(amount); | |
| } | |
| function formatDatePdf(dateStr: string): string { | |
| try { | |
| return new Intl.DateTimeFormat('fr-CA', { | |
| year: 'numeric', | |
| month: '2-digit', | |
| day: '2-digit', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| }).format(new Date(dateStr)); | |
| } catch { | |
| return dateStr; | |
| } | |
| } | |
| const STATUS_LABELS: Record<string, string> = { | |
| deposited: 'Déposé ✓', | |
| pending: 'En attente', | |
| expired: 'Expiré', | |
| cancelled: 'Annulé', | |
| }; | |
| export function generateReceipt(transaction: InteracTransaction): Promise<Buffer> { | |
| return new Promise((resolve, reject) => { | |
| const doc = new PDFDocument({ | |
| size: 'A4', | |
| margin: 50, | |
| info: { | |
| Title: `Reçu - ${transaction.reference || 'N/A'}`, | |
| Author: 'ICC Interac Manager', | |
| }, | |
| }); | |
| const chunks: Buffer[] = []; | |
| doc.on('data', (chunk: Buffer) => chunks.push(chunk)); | |
| doc.on('end', () => resolve(Buffer.concat(chunks))); | |
| doc.on('error', reject); | |
| const pageWidth = doc.page.width - 100; | |
| const startX = 50; | |
| let y = 50; | |
| // Header background | |
| doc.rect(startX, y, pageWidth, 80).fill(ICC_BLUE); | |
| // Header text | |
| doc.fillColor('#FFFFFF') | |
| .font('Helvetica-Bold') | |
| .fontSize(22) | |
| .text('ICC AMÉRIQUES', startX, y + 15, { width: pageWidth, align: 'center' }); | |
| doc.fontSize(12) | |
| .font('Helvetica') | |
| .text('Reçu de virement Interac', startX, y + 45, { width: pageWidth, align: 'center' }); | |
| y += 100; | |
| // Separator line | |
| doc.moveTo(startX, y).lineTo(startX + pageWidth, y).strokeColor(ICC_BLUE).lineWidth(2).stroke(); | |
| y += 20; | |
| // Transaction details | |
| const fields: [string, string][] = [ | |
| ['Date', formatDatePdf(transaction.date)], | |
| ['Expéditeur', transaction.sender], | |
| ['Montant', formatCurrencyPdf(transaction.amount)], | |
| ['Devise', transaction.currency || 'CAD'], | |
| ['Référence', transaction.reference || 'N/A'], | |
| ['Message', transaction.message || '—'], | |
| ['Succursale', (transaction as any).branch || 'Montreal'], | |
| ['Statut', STATUS_LABELS[transaction.status] || transaction.status], | |
| ]; | |
| for (const [label, value] of fields) { | |
| // Alternating row background | |
| if (fields.indexOf([label, value] as any) % 2 === 0) { | |
| doc.rect(startX, y - 5, pageWidth, 30).fill(LIGHT_GRAY); | |
| } | |
| doc.fillColor(GRAY_TEXT) | |
| .font('Helvetica-Bold') | |
| .fontSize(11) | |
| .text(`${label}:`, startX + 15, y, { width: 150 }); | |
| doc.fillColor('#111827') | |
| .font('Helvetica') | |
| .fontSize(11) | |
| .text(value, startX + 170, y, { width: pageWidth - 185 }); | |
| y += 30; | |
| } | |
| y += 10; | |
| // Footer separator | |
| doc.moveTo(startX, y).lineTo(startX + pageWidth, y).strokeColor('#E5E7EB').lineWidth(1).stroke(); | |
| y += 15; | |
| // Footer | |
| doc.fillColor(GRAY_TEXT) | |
| .font('Helvetica') | |
| .fontSize(9) | |
| .text('Reçu généré automatiquement par ICC Interac Manager', startX, y, { width: pageWidth, align: 'center' }); | |
| y += 15; | |
| doc.text(`Date de génération: ${new Date().toISOString().split('T')[0]}`, startX, y, { width: pageWidth, align: 'center' }); | |
| doc.end(); | |
| }); | |
| } | |
| export async function generateBatchReceipts(txns: InteracTransaction[]): Promise<Buffer> { | |
| const doc = new PDFDocument({ size: 'A4', margin: 50 }); | |
| const chunks: Buffer[] = []; | |
| return new Promise((resolve, reject) => { | |
| doc.on('data', (chunk: Buffer) => chunks.push(chunk)); | |
| doc.on('end', () => resolve(Buffer.concat(chunks))); | |
| doc.on('error', reject); | |
| for (let i = 0; i < txns.length; i++) { | |
| if (i > 0) doc.addPage(); | |
| const tx = txns[i]; | |
| const pageWidth = doc.page.width - 100; | |
| const startX = 50; | |
| let y = 50; | |
| // Header | |
| doc.rect(startX, y, pageWidth, 60).fill(ICC_BLUE); | |
| doc.fillColor('#FFFFFF').font('Helvetica-Bold').fontSize(18) | |
| .text('ICC AMÉRIQUES', startX, y + 10, { width: pageWidth, align: 'center' }); | |
| doc.fontSize(10).font('Helvetica') | |
| .text('Reçu de virement Interac', startX, y + 35, { width: pageWidth, align: 'center' }); | |
| y += 75; | |
| const fields: [string, string][] = [ | |
| ['Date', formatDatePdf(tx.date)], | |
| ['Expéditeur', tx.sender], | |
| ['Montant', formatCurrencyPdf(tx.amount)], | |
| ['Référence', tx.reference || 'N/A'], | |
| ['Message', tx.message || '—'], | |
| ['Succursale', (tx as any).branch || 'Montreal'], | |
| ['Statut', STATUS_LABELS[tx.status] || tx.status], | |
| ]; | |
| for (const [label, value] of fields) { | |
| doc.fillColor(GRAY_TEXT).font('Helvetica-Bold').fontSize(10) | |
| .text(`${label}:`, startX + 15, y, { width: 130 }); | |
| doc.fillColor('#111827').font('Helvetica').fontSize(10) | |
| .text(value, startX + 150, y, { width: pageWidth - 165 }); | |
| y += 25; | |
| } | |
| y += 10; | |
| doc.fillColor(GRAY_TEXT).font('Helvetica').fontSize(8) | |
| .text(`Reçu ${i + 1}/${txns.length} — Généré le ${new Date().toISOString().split('T')[0]}`, | |
| startX, y, { width: pageWidth, align: 'center' }); | |
| } | |
| doc.end(); | |
| }); | |
| } | |