opencrm/backend/src/services/pdfService.ts

175 lines
4.1 KiB
TypeScript

// ==================== PDF SERVICE ====================
import PDFDocument from 'pdfkit';
interface EmailData {
from: string;
to: string;
cc?: string;
subject: string;
date: Date;
bodyText?: string;
bodyHtml?: string;
}
/**
* Generiert ein PDF aus einer E-Mail
*/
export async function generateEmailPdf(email: EmailData): Promise<Buffer> {
return new Promise((resolve, reject) => {
try {
const doc = new PDFDocument({
size: 'A4',
margins: { top: 50, bottom: 50, left: 50, right: 50 },
});
const chunks: Buffer[] = [];
doc.on('data', (chunk) => chunks.push(chunk));
doc.on('end', () => resolve(Buffer.concat(chunks)));
doc.on('error', reject);
// Header
doc
.fontSize(18)
.font('Helvetica-Bold')
.text('E-Mail', { align: 'center' });
doc.moveDown(1.5);
// Metadaten-Tabelle
doc.fontSize(10).font('Helvetica');
// Von
doc
.font('Helvetica-Bold')
.text('Von: ', { continued: true })
.font('Helvetica')
.text(email.from);
// An
doc
.font('Helvetica-Bold')
.text('An: ', { continued: true })
.font('Helvetica')
.text(email.to);
// CC (falls vorhanden)
if (email.cc) {
doc
.font('Helvetica-Bold')
.text('CC: ', { continued: true })
.font('Helvetica')
.text(email.cc);
}
// Datum
const formattedDate = email.date.toLocaleString('de-DE', {
dateStyle: 'full',
timeStyle: 'short',
});
doc
.font('Helvetica-Bold')
.text('Datum: ', { continued: true })
.font('Helvetica')
.text(formattedDate);
// Betreff
doc
.font('Helvetica-Bold')
.text('Betreff: ', { continued: true })
.font('Helvetica')
.text(email.subject || '(Kein Betreff)');
doc.moveDown(1);
// Trennlinie
doc
.moveTo(50, doc.y)
.lineTo(doc.page.width - 50, doc.y)
.stroke();
doc.moveDown(1);
// Inhalt
doc.fontSize(11);
// HTML in Text konvertieren (vereinfacht)
let content = email.bodyText || '';
if (!content && email.bodyHtml) {
// Einfache HTML-zu-Text Konvertierung
content = htmlToPlainText(email.bodyHtml);
}
if (!content) {
content = '(Kein Inhalt)';
}
// Text mit Zeilenumbrüchen ausgeben
doc.text(content, {
align: 'left',
lineGap: 2,
});
// Footer mit Erstellungsdatum
const footerY = doc.page.height - 40;
doc
.fontSize(8)
.fillColor('#666666')
.text(
`Exportiert am ${new Date().toLocaleString('de-DE')}`,
50,
footerY,
{ align: 'center', width: doc.page.width - 100 }
);
doc.end();
} catch (error) {
reject(error);
}
});
}
/**
* Konvertiert HTML in Plain-Text (vereinfacht)
*/
function htmlToPlainText(html: string): string {
let text = html;
// Zeilenumbrüche vor Block-Elementen
text = text.replace(/<(br|p|div|h[1-6]|li|tr)[^>]*>/gi, '\n');
// Listen-Elemente
text = text.replace(/<li[^>]*>/gi, '\n• ');
// Links mit URL
text = text.replace(/<a[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, '$2 ($1)');
// Alle anderen Tags entfernen
text = text.replace(/<[^>]+>/g, '');
// HTML-Entities dekodieren
text = text.replace(/&nbsp;/g, ' ');
text = text.replace(/&amp;/g, '&');
text = text.replace(/&lt;/g, '<');
text = text.replace(/&gt;/g, '>');
text = text.replace(/&quot;/g, '"');
text = text.replace(/&#39;/g, "'");
text = text.replace(/&auml;/g, 'ä');
text = text.replace(/&ouml;/g, 'ö');
text = text.replace(/&uuml;/g, 'ü');
text = text.replace(/&Auml;/g, 'Ä');
text = text.replace(/&Ouml;/g, 'Ö');
text = text.replace(/&Uuml;/g, 'Ü');
text = text.replace(/&szlig;/g, 'ß');
// Mehrfache Leerzeilen reduzieren
text = text.replace(/\n{3,}/g, '\n\n');
// Führende/folgende Leerzeichen entfernen
text = text.trim();
return text;
}