UNPKG

@casoon/invoice-generator

Version:

Generate PDF invoices, XRechnung XML, and ZUGFeRD PDF/A-3 from JSON data with TypeScript support

141 lines (116 loc) 5.08 kB
import PDFKit from 'pdfkit'; import { Invoice } from '../../types/invoice.types'; import path from 'path'; export class HeaderSection { private doc: PDFKit.PDFDocument; private currentY: number; private logoPath?: string; constructor(doc: PDFKit.PDFDocument, currentY: number, logoPath?: string) { this.doc = doc; this.currentY = currentY; this.logoPath = logoPath || 'logo.png'; // Standard: logo.png im Hauptverzeichnis } /** * Bereich 1: Header mit Adressen und Rechnungsdaten * DIN 5008: Sender links oben, Empfänger links unten, Rechnungsdaten rechts */ public draw(invoice: Invoice): number { // Logo-Bereich (oben) this.drawLogoArea(); // Rechnungsdaten-Bereich (rechts) - wie im Bild this.drawInvoiceDetailsArea(invoice); // Empfänger-Adresse (links unten) - DIN 5008 this.drawRecipientAddressArea(invoice); // Trennlinie nach Header this.drawSeparatorLine(); return this.currentY; } private drawInvoiceDetailsArea(invoice: Invoice): void { const rightX = 350; // Alle Bereiche rechts let y = this.currentY - 80; // Zurück zum Anfang // Logo ist bereits oben rechts positioniert (x=350) y += 80; // Platz für Logo // Rückfragen an (rechts, kleinere Schrift) this.doc.font('Helvetica-Bold') .fontSize(8) // Kleinere Schrift .text('Rückfragen an:', rightX, y, { continued: false }); y += 12; // Kleinerer Abstand this.doc.font('Helvetica') .fontSize(8) // Kleinere Schrift .text(invoice.sender.name, rightX, y, { continued: false }); y += 12; this.doc.text(`Tel: ${invoice.sender.contactInfo.phone}`, rightX, y, { continued: false }); y += 12; this.doc.text(`E-Mail: ${invoice.sender.contactInfo.email}`, rightX, y, { continued: false }); y += 15; // Abstand zwischen den Bereichen // Rechnungsdaten (rechts unten) this.doc.font('Helvetica') .fontSize(10) .text(`Rechnungs-Nr.: ${invoice.details.invoiceNumber}`, rightX, y, { continued: false }); y += 15; this.doc.text(`Kunden-Nr.: ${invoice.details.customerNumber}`, rightX, y, { continued: false }); y += 15; this.doc.text(`Rechnungsdatum: ${invoice.details.invoiceDate}`, rightX, y, { continued: false }); y += 15; this.doc.text(`Lieferdatum: ${invoice.details.deliveryDate}`, rightX, y, { continued: false }); y += 15; this.doc.text(`Leistungszeitraum: ${invoice.details.servicePeriod}`, rightX, y, { continued: false }); } private drawLogoArea(): void { // Logo aus logoPath oder Fallback if (this.logoPath) { try { // Versuche verschiedene Pfad-Formate let logoToLoad = this.logoPath; // Wenn es ein relativer Pfad ist, mache ihn absolut if (!path.isAbsolute(this.logoPath)) { logoToLoad = path.join(process.cwd(), this.logoPath); } console.log('Versuche Logo zu laden von:', logoToLoad); this.doc.image(logoToLoad, 350, this.currentY, { width: 150 }); console.log('Logo erfolgreich geladen!'); } catch (error) { console.warn('Logo konnte nicht geladen werden:', error); this.drawLogoPlaceholder(); } } else { this.drawLogoPlaceholder(); } this.currentY += 80; // Platz für Logo } private drawLogoPlaceholder(): void { // Sauberer Logo-Platzhalter (rechts) this.doc.font('Helvetica-Bold') .fontSize(16) .text('LOGO', 350, this.currentY + 30, { continued: false }); this.doc.rect(350, this.currentY, 150, 60) .stroke(); } private drawRecipientAddressArea(invoice: Invoice): void { // Empfänger-Adresse links unten - DIN 5008 // Mindestens 45mm vom oberen Rand const recipientY = Math.max(this.currentY, 160); // 160px entspricht etwa 45mm // Rechnungssteller in einer Zeile über dem Empfänger - DIN 5008 const senderLine = [ invoice.sender.name, invoice.sender.address.street, `${invoice.sender.address.postalCode} ${invoice.sender.address.city}` ].filter(Boolean).join(' · '); this.doc.font('Helvetica') .fontSize(8) // Kleinere Schrift für Rechnungssteller .text(senderLine, 50, recipientY - 20, { continued: false }); // Rechnungsempfänger - kleinere Schrift und Zeilenhöhe this.doc.fontSize(12) // Kleinere Schrift (vorher 14) .text(invoice.recipient.name, 50, recipientY, { continued: false }); this.doc.fontSize(12) // Kleinere Schrift .text(invoice.recipient.address.street, 50, recipientY + 15, { continued: false }); // Kleinere Zeilenhöhe (vorher 20) this.doc.text(`${invoice.recipient.address.postalCode} ${invoice.recipient.address.city}`, 50, recipientY + 30, { continued: false }); // Kleinere Zeilenhöhe (vorher 40) // Aktualisiere currentY für den nächsten Bereich this.currentY = recipientY + 55; // Kleinere Gesamthöhe (vorher 70) } private drawSeparatorLine(): void { this.currentY += 20; // Keine Trennlinie nach Header this.currentY += 20; } }