@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
text/typescript
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;
}
}