import * as plugins from './plugins.js'; import * as path from 'path'; import type { ITrialBalanceReport, IIncomeStatement, IBalanceSheet } from './skr.types.js'; export interface IPdfReportOptions { companyName: string; companyAddress?: string; taxId?: string; registrationNumber?: string; fiscalYear: number; dateFrom: Date; dateTo: Date; preparedBy?: string; preparedDate?: Date; } export class PdfReportGenerator { private exportPath: string; private options: IPdfReportOptions; private pdfInstance: plugins.smartpdf.SmartPdf | null = null; constructor(exportPath: string, options: IPdfReportOptions) { this.exportPath = exportPath; this.options = options; } /** * Initializes the PDF generator */ public async initialize(): Promise { this.pdfInstance = new plugins.smartpdf.SmartPdf(); await this.pdfInstance.start(); } /** * Generates the trial balance PDF report */ public async generateTrialBalancePdf(report: ITrialBalanceReport): Promise { if (!this.pdfInstance) { throw new Error('PDF generator not initialized'); } const html = this.generateTrialBalanceHtml(report); const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html); return Buffer.from(pdfResult.buffer); } /** * Generates the income statement PDF report */ public async generateIncomeStatementPdf(report: IIncomeStatement): Promise { if (!this.pdfInstance) { throw new Error('PDF generator not initialized'); } const html = this.generateIncomeStatementHtml(report); const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html); return Buffer.from(pdfResult.buffer); } /** * Generates the balance sheet PDF report */ public async generateBalanceSheetPdf(report: IBalanceSheet): Promise { if (!this.pdfInstance) { throw new Error('PDF generator not initialized'); } const html = this.generateBalanceSheetHtml(report); const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html); return Buffer.from(pdfResult.buffer); } /** * Generates the comprehensive Jahresabschluss PDF */ public async generateJahresabschlussPdf( trialBalance: ITrialBalanceReport, incomeStatement: IIncomeStatement, balanceSheet: IBalanceSheet ): Promise { if (!this.pdfInstance) { throw new Error('PDF generator not initialized'); } const html = this.generateJahresabschlussHtml(trialBalance, incomeStatement, balanceSheet); const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html); return Buffer.from(pdfResult.buffer); } /** * Generates HTML for trial balance report */ private generateTrialBalanceHtml(report: ITrialBalanceReport): string { const entries = report.entries || []; const tableRows = entries.map(entry => ` ${entry.accountNumber} ${entry.accountName} ${this.formatGermanNumber(0)} ${this.formatGermanNumber(entry.debitBalance)} ${this.formatGermanNumber(entry.creditBalance)} ${this.formatGermanNumber(entry.netBalance)} `).join(''); return ` ${this.generateHeader('Summen- und Saldenliste')} ${tableRows}
Konto Bezeichnung Anfangssaldo Soll Haben Saldo
Summe ${this.formatGermanNumber(report.totalDebits)} ${this.formatGermanNumber(report.totalCredits)} ${this.formatGermanNumber(report.totalDebits - report.totalCredits)}
${this.generateFooter()} `; } /** * Generates HTML for income statement report */ private generateIncomeStatementHtml(report: IIncomeStatement): string { const revenueRows = (report.revenue || []).map(entry => ` ${entry.accountNumber} ${entry.accountName} ${this.formatGermanNumber(entry.amount)} `).join(''); const expenseRows = (report.expenses || []).map(entry => ` ${entry.accountNumber} ${entry.accountName} ${this.formatGermanNumber(entry.amount)} `).join(''); return ` ${this.generateHeader('Gewinn- und Verlustrechnung')}

Erträge

${revenueRows}
Konto Bezeichnung Betrag
Summe Erträge ${this.formatGermanNumber(report.totalRevenue)}

Aufwendungen

${expenseRows}
Konto Bezeichnung Betrag
Summe Aufwendungen ${this.formatGermanNumber(report.totalExpenses)}

Ergebnis

Erträge ${this.formatGermanNumber(report.totalRevenue)}
Aufwendungen - ${this.formatGermanNumber(report.totalExpenses)}
${report.netIncome >= 0 ? 'Jahresüberschuss' : 'Jahresfehlbetrag'} ${this.formatGermanNumber(report.netIncome)}
${this.generateFooter()} `; } /** * Generates HTML for balance sheet report */ private generateBalanceSheetHtml(report: IBalanceSheet): string { const assetRows = [...(report.assets.current || []), ...(report.assets.fixed || [])].map(entry => ` ${entry.accountNumber} ${entry.accountName} ${this.formatGermanNumber(entry.amount)} `).join(''); const liabilityRows = [...(report.liabilities.current || []), ...(report.liabilities.longTerm || [])].map(entry => ` ${entry.accountNumber} ${entry.accountName} ${this.formatGermanNumber(entry.amount)} `).join(''); const equityRows = (report.equity.entries || []).map(entry => ` ${entry.accountNumber} ${entry.accountName} ${this.formatGermanNumber(entry.amount)} `).join(''); return ` ${this.generateHeader('Bilanz')}

Aktiva

${assetRows}
Konto Bezeichnung Betrag
Summe Aktiva ${this.formatGermanNumber(report.assets.totalAssets)}

Passiva

Eigenkapital

${equityRows}
Summe Eigenkapital ${this.formatGermanNumber(report.equity.totalEquity)}

Fremdkapital

${liabilityRows}
Summe Fremdkapital ${this.formatGermanNumber(report.liabilities.totalLiabilities)}
Summe Passiva ${this.formatGermanNumber(report.liabilities.totalLiabilities + report.equity.totalEquity)}
${this.generateFooter()} `; } /** * Generates comprehensive Jahresabschluss HTML */ private generateJahresabschlussHtml( trialBalance: ITrialBalanceReport, incomeStatement: IIncomeStatement, balanceSheet: IBalanceSheet ): string { return `

Jahresabschluss

${this.options.companyName}

Geschäftsjahr ${this.options.fiscalYear}

${this.formatGermanDate(this.options.dateFrom)} bis ${this.formatGermanDate(this.options.dateTo)}

Inhalt

  • 1. Bilanz
  • 2. Gewinn- und Verlustrechnung
  • 3. Summen- und Saldenliste
${this.generateBalanceSheetHtml(balanceSheet)}
${this.generateIncomeStatementHtml(incomeStatement)}
${this.generateTrialBalanceHtml(trialBalance)} `; } /** * Generates the report header */ private generateHeader(reportTitle: string): string { return `

${this.options.companyName}

${this.options.companyAddress ? `

${this.options.companyAddress}

` : ''} ${this.options.taxId ? `

Steuernummer: ${this.options.taxId}

` : ''} ${this.options.registrationNumber ? `

Handelsregister: ${this.options.registrationNumber}

` : ''}

${reportTitle}

Periode: ${this.formatGermanDate(this.options.dateFrom)} bis ${this.formatGermanDate(this.options.dateTo)}

`; } /** * Generates the report footer */ private generateFooter(): string { const preparedDate = this.options.preparedDate || new Date(); return ` `; } /** * Gets the base CSS styles for all reports */ private getBaseStyles(): string { return ` body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 40px; color: #333; line-height: 1.6; } h1 { color: #2c3e50; margin-bottom: 10px; } h2 { color: #34495e; margin-top: 30px; margin-bottom: 15px; } h3 { color: #7f8c8d; margin-top: 20px; margin-bottom: 10px; } .header { text-align: center; margin-bottom: 40px; } .footer { margin-top: 50px; text-align: center; font-size: 12px; color: #7f8c8d; } .disclaimer { margin-top: 20px; font-style: italic; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th { background-color: #34495e; color: white; padding: 10px; text-align: left; font-weight: 600; } td { padding: 8px; border-bottom: 1px solid #ecf0f1; } tbody tr:hover { background-color: #f8f9fa; } .number { text-align: right; font-family: 'Courier New', monospace; } .total-row { font-weight: bold; background-color: #ecf0f1; } .subtotal-row { font-weight: 600; background-color: #f8f9fa; } .positive { color: #27ae60; } .negative { color: #e74c3c; } .result-section { margin-top: 40px; padding: 20px; background-color: #f8f9fa; border-radius: 5px; } .summary-table { max-width: 500px; margin: 20px auto; } .balance-sheet { display: flex; gap: 40px; } .aktiva, .passiva { flex: 1; } @media print { body { margin: 20px; } .page-break { page-break-after: always; } } `; } /** * Formats number in German format (1.234,56) */ private formatGermanNumber(value: number): string { return value.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } /** * Formats date in German format (DD.MM.YYYY) */ private formatGermanDate(date: Date): string { return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); } /** * Saves a PDF report to the export directory */ public async savePdfReport(filename: string, pdfBuffer: Buffer): Promise { const reportsDir = path.join(this.exportPath, 'data', 'reports'); await plugins.smartfile.fs.ensureDir(reportsDir); const filePath = path.join(reportsDir, filename); await plugins.smartfile.memory.toFs(pdfBuffer, filePath); return filePath; } /** * Closes the PDF generator */ public async close(): Promise { if (this.pdfInstance) { await this.pdfInstance.stop(); this.pdfInstance = null; } } }