MichaelEdou
Set default branch to Montreal for unclassified Interac transactions
fe203ef
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();
});
}