@fin.cx/skr
Version:
SKR03 and SKR04 German accounting standards for double-entry bookkeeping
548 lines (515 loc) • 26.2 kB
JavaScript
import * as plugins from './plugins.js';
import * as path from 'path';
export class PdfReportGenerator {
constructor(exportPath, options) {
this.pdfInstance = null;
this.exportPath = exportPath;
this.options = options;
}
/**
* Initializes the PDF generator
*/
async initialize() {
this.pdfInstance = new plugins.smartpdf.SmartPdf();
await this.pdfInstance.start();
}
/**
* Generates the trial balance PDF report
*/
async generateTrialBalancePdf(report) {
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
*/
async generateIncomeStatementPdf(report) {
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
*/
async generateBalanceSheetPdf(report) {
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
*/
async generateJahresabschlussPdf(trialBalance, incomeStatement, balanceSheet) {
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
*/
generateTrialBalanceHtml(report) {
const entries = report.entries || [];
const tableRows = entries.map(entry => `
<tr>
<td>${entry.accountNumber}</td>
<td>${entry.accountName}</td>
<td class="number">${this.formatGermanNumber(0)}</td>
<td class="number">${this.formatGermanNumber(entry.debitBalance)}</td>
<td class="number">${this.formatGermanNumber(entry.creditBalance)}</td>
<td class="number">${this.formatGermanNumber(entry.netBalance)}</td>
</tr>
`).join('');
return `
<html>
<head>
<meta charset="UTF-8">
<style>
${this.getBaseStyles()}
</style>
</head>
<body>
${this.generateHeader('Summen- und Saldenliste')}
<table class="report-table">
<thead>
<tr>
<th>Konto</th>
<th>Bezeichnung</th>
<th>Anfangssaldo</th>
<th>Soll</th>
<th>Haben</th>
<th>Saldo</th>
</tr>
</thead>
<tbody>
${tableRows}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="3">Summe</td>
<td class="number">${this.formatGermanNumber(report.totalDebits)}</td>
<td class="number">${this.formatGermanNumber(report.totalCredits)}</td>
<td class="number">${this.formatGermanNumber(report.totalDebits - report.totalCredits)}</td>
</tr>
</tfoot>
</table>
${this.generateFooter()}
</body>
</html>
`;
}
/**
* Generates HTML for income statement report
*/
generateIncomeStatementHtml(report) {
const revenueRows = (report.revenue || []).map(entry => `
<tr>
<td>${entry.accountNumber}</td>
<td>${entry.accountName}</td>
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
</tr>
`).join('');
const expenseRows = (report.expenses || []).map(entry => `
<tr>
<td>${entry.accountNumber}</td>
<td>${entry.accountName}</td>
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
</tr>
`).join('');
return `
<html>
<head>
<meta charset="UTF-8">
<style>
${this.getBaseStyles()}
</style>
</head>
<body>
${this.generateHeader('Gewinn- und Verlustrechnung')}
<h2>Erträge</h2>
<table class="report-table">
<thead>
<tr>
<th>Konto</th>
<th>Bezeichnung</th>
<th>Betrag</th>
</tr>
</thead>
<tbody>
${revenueRows}
</tbody>
<tfoot>
<tr class="subtotal-row">
<td colspan="2">Summe Erträge</td>
<td class="number">${this.formatGermanNumber(report.totalRevenue)}</td>
</tr>
</tfoot>
</table>
<h2>Aufwendungen</h2>
<table class="report-table">
<thead>
<tr>
<th>Konto</th>
<th>Bezeichnung</th>
<th>Betrag</th>
</tr>
</thead>
<tbody>
${expenseRows}
</tbody>
<tfoot>
<tr class="subtotal-row">
<td colspan="2">Summe Aufwendungen</td>
<td class="number">${this.formatGermanNumber(report.totalExpenses)}</td>
</tr>
</tfoot>
</table>
<div class="result-section">
<h2>Ergebnis</h2>
<table class="summary-table">
<tr>
<td>Erträge</td>
<td class="number">${this.formatGermanNumber(report.totalRevenue)}</td>
</tr>
<tr>
<td>Aufwendungen</td>
<td class="number">- ${this.formatGermanNumber(report.totalExpenses)}</td>
</tr>
<tr class="total-row">
<td>${report.netIncome >= 0 ? 'Jahresüberschuss' : 'Jahresfehlbetrag'}</td>
<td class="number ${report.netIncome >= 0 ? 'positive' : 'negative'}">
${this.formatGermanNumber(report.netIncome)}
</td>
</tr>
</table>
</div>
${this.generateFooter()}
</body>
</html>
`;
}
/**
* Generates HTML for balance sheet report
*/
generateBalanceSheetHtml(report) {
const assetRows = [...(report.assets.current || []), ...(report.assets.fixed || [])].map(entry => `
<tr>
<td>${entry.accountNumber}</td>
<td>${entry.accountName}</td>
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
</tr>
`).join('');
const liabilityRows = [...(report.liabilities.current || []), ...(report.liabilities.longTerm || [])].map(entry => `
<tr>
<td>${entry.accountNumber}</td>
<td>${entry.accountName}</td>
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
</tr>
`).join('');
const equityRows = (report.equity.entries || []).map(entry => `
<tr>
<td>${entry.accountNumber}</td>
<td>${entry.accountName}</td>
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
</tr>
`).join('');
return `
<html>
<head>
<meta charset="UTF-8">
<style>
${this.getBaseStyles()}
</style>
</head>
<body>
${this.generateHeader('Bilanz')}
<div class="balance-sheet">
<div class="aktiva">
<h2>Aktiva</h2>
<table class="report-table">
<thead>
<tr>
<th>Konto</th>
<th>Bezeichnung</th>
<th>Betrag</th>
</tr>
</thead>
<tbody>
${assetRows}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="2">Summe Aktiva</td>
<td class="number">${this.formatGermanNumber(report.assets.totalAssets)}</td>
</tr>
</tfoot>
</table>
</div>
<div class="passiva">
<h2>Passiva</h2>
<h3>Eigenkapital</h3>
<table class="report-table">
<tbody>
${equityRows}
</tbody>
<tfoot>
<tr class="subtotal-row">
<td colspan="2">Summe Eigenkapital</td>
<td class="number">${this.formatGermanNumber(report.equity.totalEquity)}</td>
</tr>
</tfoot>
</table>
<h3>Fremdkapital</h3>
<table class="report-table">
<tbody>
${liabilityRows}
</tbody>
<tfoot>
<tr class="subtotal-row">
<td colspan="2">Summe Fremdkapital</td>
<td class="number">${this.formatGermanNumber(report.liabilities.totalLiabilities)}</td>
</tr>
</tfoot>
</table>
<table class="summary-table">
<tr class="total-row">
<td>Summe Passiva</td>
<td class="number">${this.formatGermanNumber(report.liabilities.totalLiabilities + report.equity.totalEquity)}</td>
</tr>
</table>
</div>
</div>
${this.generateFooter()}
</body>
</html>
`;
}
/**
* Generates comprehensive Jahresabschluss HTML
*/
generateJahresabschlussHtml(trialBalance, incomeStatement, balanceSheet) {
return `
<html>
<head>
<meta charset="UTF-8">
<style>
${this.getBaseStyles()}
.page-break { page-break-after: always; }
.cover-page {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
}
.cover-page h1 { font-size: 36px; margin-bottom: 20px; }
.cover-page h2 { font-size: 24px; margin-bottom: 40px; }
.toc { margin-top: 50px; }
.toc h2 { margin-bottom: 20px; }
.toc ul { list-style: none; padding: 0; }
.toc li { margin: 10px 0; font-size: 16px; }
</style>
</head>
<body>
<div class="cover-page">
<h1>Jahresabschluss</h1>
<h2>${this.options.companyName}</h2>
<p>Geschäftsjahr ${this.options.fiscalYear}</p>
<p>${this.formatGermanDate(this.options.dateFrom)} bis ${this.formatGermanDate(this.options.dateTo)}</p>
<div class="toc">
<h2>Inhalt</h2>
<ul>
<li>1. Bilanz</li>
<li>2. Gewinn- und Verlustrechnung</li>
<li>3. Summen- und Saldenliste</li>
</ul>
</div>
</div>
<div class="page-break"></div>
${this.generateBalanceSheetHtml(balanceSheet)}
<div class="page-break"></div>
${this.generateIncomeStatementHtml(incomeStatement)}
<div class="page-break"></div>
${this.generateTrialBalanceHtml(trialBalance)}
</body>
</html>
`;
}
/**
* Generates the report header
*/
generateHeader(reportTitle) {
return `
<div class="header">
<h1>${this.options.companyName}</h1>
${this.options.companyAddress ? `<p>${this.options.companyAddress}</p>` : ''}
${this.options.taxId ? `<p>Steuernummer: ${this.options.taxId}</p>` : ''}
${this.options.registrationNumber ? `<p>Handelsregister: ${this.options.registrationNumber}</p>` : ''}
<hr>
<h2>${reportTitle}</h2>
<p>Periode: ${this.formatGermanDate(this.options.dateFrom)} bis ${this.formatGermanDate(this.options.dateTo)}</p>
</div>
`;
}
/**
* Generates the report footer
*/
generateFooter() {
const preparedDate = this.options.preparedDate || new Date();
return `
<div class="footer">
<hr>
<p>Erstellt am: ${this.formatGermanDate(preparedDate)}</p>
${this.options.preparedBy ? `<p>Erstellt von: ${this.options.preparedBy}</p>` : ''}
<p class="disclaimer">
Dieser Bericht wurde automatisch generiert und ist Teil des revisionssicheren
Jahresabschluss-Exports gemäß GoBD.
</p>
</div>
`;
}
/**
* Gets the base CSS styles for all reports
*/
getBaseStyles() {
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)
*/
formatGermanNumber(value) {
return value.toLocaleString('de-DE', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
/**
* Formats date in German format (DD.MM.YYYY)
*/
formatGermanDate(date) {
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
}
/**
* Saves a PDF report to the export directory
*/
async savePdfReport(filename, pdfBuffer) {
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
*/
async close() {
if (this.pdfInstance) {
await this.pdfInstance.stop();
this.pdfInstance = null;
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tyLmV4cG9ydC5wZGYuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9za3IuZXhwb3J0LnBkZi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQWU3QixNQUFNLE9BQU8sa0JBQWtCO0lBSzdCLFlBQVksVUFBa0IsRUFBRSxPQUEwQjtRQUZsRCxnQkFBVyxHQUFxQyxJQUFJLENBQUM7UUFHM0QsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7UUFDN0IsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDekIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVU7UUFDckIsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDbkQsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ2pDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxNQUEyQjtRQUM5RCxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ3RCLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztRQUNuRCxDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLHdCQUF3QixDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ25ELE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQywyQkFBMkIsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMzRSxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQywwQkFBMEIsQ0FBQyxNQUF3QjtRQUM5RCxJQUFJLENBQUMs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