@fin.cx/skr 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/index.d.ts +6 -0
- package/dist_ts/index.js +7 -1
- package/dist_ts/plugins.d.ts +8 -1
- package/dist_ts/plugins.js +10 -2
- package/dist_ts/skr.api.d.ts +70 -0
- package/dist_ts/skr.api.js +354 -3
- package/dist_ts/skr.classes.journalentry.d.ts +1 -0
- package/dist_ts/skr.classes.journalentry.js +55 -15
- package/dist_ts/skr.classes.ledger.d.ts +4 -0
- package/dist_ts/skr.classes.ledger.js +72 -1
- package/dist_ts/skr.classes.reports.js +56 -22
- package/dist_ts/skr.export.accounts.d.ts +53 -0
- package/dist_ts/skr.export.accounts.js +111 -0
- package/dist_ts/skr.export.balances.d.ts +59 -0
- package/dist_ts/skr.export.balances.js +205 -0
- package/dist_ts/skr.export.d.ts +110 -0
- package/dist_ts/skr.export.js +315 -0
- package/dist_ts/skr.export.ledger.d.ts +95 -0
- package/dist_ts/skr.export.ledger.js +164 -0
- package/dist_ts/skr.export.pdf.d.ts +82 -0
- package/dist_ts/skr.export.pdf.js +548 -0
- package/dist_ts/skr.invoice.adapter.d.ts +98 -0
- package/dist_ts/skr.invoice.adapter.js +476 -0
- package/dist_ts/skr.invoice.booking.d.ts +102 -0
- package/dist_ts/skr.invoice.booking.js +556 -0
- package/dist_ts/skr.invoice.entity.d.ts +287 -0
- package/dist_ts/skr.invoice.entity.js +2 -0
- package/dist_ts/skr.invoice.mapper.d.ts +69 -0
- package/dist_ts/skr.invoice.mapper.js +401 -0
- package/dist_ts/skr.invoice.storage.d.ts +140 -0
- package/dist_ts/skr.invoice.storage.js +529 -0
- package/dist_ts/skr.security.d.ts +65 -0
- package/dist_ts/skr.security.js +319 -0
- package/dist_ts/skr.types.d.ts +1 -0
- package/package.json +18 -12
- package/readme.md +461 -132
- package/ts/index.ts +6 -0
- package/ts/plugins.ts +22 -1
- package/ts/skr.api.ts +489 -2
- package/ts/skr.classes.journalentry.ts +63 -14
- package/ts/skr.classes.ledger.ts +85 -0
- package/ts/skr.classes.reports.ts +64 -21
- package/ts/skr.export.accounts.ts +154 -0
- package/ts/skr.export.balances.ts +270 -0
- package/ts/skr.export.ledger.ts +249 -0
- package/ts/skr.export.pdf.ts +601 -0
- package/ts/skr.export.ts +443 -0
- package/ts/skr.invoice.adapter.ts +581 -0
- package/ts/skr.invoice.booking.ts +738 -0
- package/ts/skr.invoice.entity.ts +351 -0
- package/ts/skr.invoice.mapper.ts +486 -0
- package/ts/skr.invoice.storage.ts +710 -0
- package/ts/skr.security.ts +405 -0
- package/ts/skr.types.ts +1 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { ITrialBalanceReport, IIncomeStatement, IBalanceSheet } from './skr.types.js';
|
|
4
|
+
|
|
5
|
+
export interface IPdfReportOptions {
|
|
6
|
+
companyName: string;
|
|
7
|
+
companyAddress?: string;
|
|
8
|
+
taxId?: string;
|
|
9
|
+
registrationNumber?: string;
|
|
10
|
+
fiscalYear: number;
|
|
11
|
+
dateFrom: Date;
|
|
12
|
+
dateTo: Date;
|
|
13
|
+
preparedBy?: string;
|
|
14
|
+
preparedDate?: Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class PdfReportGenerator {
|
|
18
|
+
private exportPath: string;
|
|
19
|
+
private options: IPdfReportOptions;
|
|
20
|
+
private pdfInstance: plugins.smartpdf.SmartPdf | null = null;
|
|
21
|
+
|
|
22
|
+
constructor(exportPath: string, options: IPdfReportOptions) {
|
|
23
|
+
this.exportPath = exportPath;
|
|
24
|
+
this.options = options;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initializes the PDF generator
|
|
29
|
+
*/
|
|
30
|
+
public async initialize(): Promise<void> {
|
|
31
|
+
this.pdfInstance = new plugins.smartpdf.SmartPdf();
|
|
32
|
+
await this.pdfInstance.start();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generates the trial balance PDF report
|
|
37
|
+
*/
|
|
38
|
+
public async generateTrialBalancePdf(report: ITrialBalanceReport): Promise<Buffer> {
|
|
39
|
+
if (!this.pdfInstance) {
|
|
40
|
+
throw new Error('PDF generator not initialized');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const html = this.generateTrialBalanceHtml(report);
|
|
44
|
+
const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html);
|
|
45
|
+
return Buffer.from(pdfResult.buffer);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generates the income statement PDF report
|
|
50
|
+
*/
|
|
51
|
+
public async generateIncomeStatementPdf(report: IIncomeStatement): Promise<Buffer> {
|
|
52
|
+
if (!this.pdfInstance) {
|
|
53
|
+
throw new Error('PDF generator not initialized');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const html = this.generateIncomeStatementHtml(report);
|
|
57
|
+
const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html);
|
|
58
|
+
return Buffer.from(pdfResult.buffer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generates the balance sheet PDF report
|
|
63
|
+
*/
|
|
64
|
+
public async generateBalanceSheetPdf(report: IBalanceSheet): Promise<Buffer> {
|
|
65
|
+
if (!this.pdfInstance) {
|
|
66
|
+
throw new Error('PDF generator not initialized');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const html = this.generateBalanceSheetHtml(report);
|
|
70
|
+
const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html);
|
|
71
|
+
return Buffer.from(pdfResult.buffer);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generates the comprehensive Jahresabschluss PDF
|
|
76
|
+
*/
|
|
77
|
+
public async generateJahresabschlussPdf(
|
|
78
|
+
trialBalance: ITrialBalanceReport,
|
|
79
|
+
incomeStatement: IIncomeStatement,
|
|
80
|
+
balanceSheet: IBalanceSheet
|
|
81
|
+
): Promise<Buffer> {
|
|
82
|
+
if (!this.pdfInstance) {
|
|
83
|
+
throw new Error('PDF generator not initialized');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const html = this.generateJahresabschlussHtml(trialBalance, incomeStatement, balanceSheet);
|
|
87
|
+
const pdfResult = await this.pdfInstance.getA4PdfResultForHtmlString(html);
|
|
88
|
+
return Buffer.from(pdfResult.buffer);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generates HTML for trial balance report
|
|
93
|
+
*/
|
|
94
|
+
private generateTrialBalanceHtml(report: ITrialBalanceReport): string {
|
|
95
|
+
const entries = report.entries || [];
|
|
96
|
+
|
|
97
|
+
const tableRows = entries.map(entry => `
|
|
98
|
+
<tr>
|
|
99
|
+
<td>${entry.accountNumber}</td>
|
|
100
|
+
<td>${entry.accountName}</td>
|
|
101
|
+
<td class="number">${this.formatGermanNumber(0)}</td>
|
|
102
|
+
<td class="number">${this.formatGermanNumber(entry.debitBalance)}</td>
|
|
103
|
+
<td class="number">${this.formatGermanNumber(entry.creditBalance)}</td>
|
|
104
|
+
<td class="number">${this.formatGermanNumber(entry.netBalance)}</td>
|
|
105
|
+
</tr>
|
|
106
|
+
`).join('');
|
|
107
|
+
|
|
108
|
+
return `
|
|
109
|
+
<!DOCTYPE html>
|
|
110
|
+
<html>
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="UTF-8">
|
|
113
|
+
<style>
|
|
114
|
+
${this.getBaseStyles()}
|
|
115
|
+
</style>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
${this.generateHeader('Summen- und Saldenliste')}
|
|
119
|
+
|
|
120
|
+
<table class="report-table">
|
|
121
|
+
<thead>
|
|
122
|
+
<tr>
|
|
123
|
+
<th>Konto</th>
|
|
124
|
+
<th>Bezeichnung</th>
|
|
125
|
+
<th>Anfangssaldo</th>
|
|
126
|
+
<th>Soll</th>
|
|
127
|
+
<th>Haben</th>
|
|
128
|
+
<th>Saldo</th>
|
|
129
|
+
</tr>
|
|
130
|
+
</thead>
|
|
131
|
+
<tbody>
|
|
132
|
+
${tableRows}
|
|
133
|
+
</tbody>
|
|
134
|
+
<tfoot>
|
|
135
|
+
<tr class="total-row">
|
|
136
|
+
<td colspan="3">Summe</td>
|
|
137
|
+
<td class="number">${this.formatGermanNumber(report.totalDebits)}</td>
|
|
138
|
+
<td class="number">${this.formatGermanNumber(report.totalCredits)}</td>
|
|
139
|
+
<td class="number">${this.formatGermanNumber(report.totalDebits - report.totalCredits)}</td>
|
|
140
|
+
</tr>
|
|
141
|
+
</tfoot>
|
|
142
|
+
</table>
|
|
143
|
+
|
|
144
|
+
${this.generateFooter()}
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generates HTML for income statement report
|
|
152
|
+
*/
|
|
153
|
+
private generateIncomeStatementHtml(report: IIncomeStatement): string {
|
|
154
|
+
const revenueRows = (report.revenue || []).map(entry => `
|
|
155
|
+
<tr>
|
|
156
|
+
<td>${entry.accountNumber}</td>
|
|
157
|
+
<td>${entry.accountName}</td>
|
|
158
|
+
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
|
|
159
|
+
</tr>
|
|
160
|
+
`).join('');
|
|
161
|
+
|
|
162
|
+
const expenseRows = (report.expenses || []).map(entry => `
|
|
163
|
+
<tr>
|
|
164
|
+
<td>${entry.accountNumber}</td>
|
|
165
|
+
<td>${entry.accountName}</td>
|
|
166
|
+
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
|
|
167
|
+
</tr>
|
|
168
|
+
`).join('');
|
|
169
|
+
|
|
170
|
+
return `
|
|
171
|
+
<!DOCTYPE html>
|
|
172
|
+
<html>
|
|
173
|
+
<head>
|
|
174
|
+
<meta charset="UTF-8">
|
|
175
|
+
<style>
|
|
176
|
+
${this.getBaseStyles()}
|
|
177
|
+
</style>
|
|
178
|
+
</head>
|
|
179
|
+
<body>
|
|
180
|
+
${this.generateHeader('Gewinn- und Verlustrechnung')}
|
|
181
|
+
|
|
182
|
+
<h2>Erträge</h2>
|
|
183
|
+
<table class="report-table">
|
|
184
|
+
<thead>
|
|
185
|
+
<tr>
|
|
186
|
+
<th>Konto</th>
|
|
187
|
+
<th>Bezeichnung</th>
|
|
188
|
+
<th>Betrag</th>
|
|
189
|
+
</tr>
|
|
190
|
+
</thead>
|
|
191
|
+
<tbody>
|
|
192
|
+
${revenueRows}
|
|
193
|
+
</tbody>
|
|
194
|
+
<tfoot>
|
|
195
|
+
<tr class="subtotal-row">
|
|
196
|
+
<td colspan="2">Summe Erträge</td>
|
|
197
|
+
<td class="number">${this.formatGermanNumber(report.totalRevenue)}</td>
|
|
198
|
+
</tr>
|
|
199
|
+
</tfoot>
|
|
200
|
+
</table>
|
|
201
|
+
|
|
202
|
+
<h2>Aufwendungen</h2>
|
|
203
|
+
<table class="report-table">
|
|
204
|
+
<thead>
|
|
205
|
+
<tr>
|
|
206
|
+
<th>Konto</th>
|
|
207
|
+
<th>Bezeichnung</th>
|
|
208
|
+
<th>Betrag</th>
|
|
209
|
+
</tr>
|
|
210
|
+
</thead>
|
|
211
|
+
<tbody>
|
|
212
|
+
${expenseRows}
|
|
213
|
+
</tbody>
|
|
214
|
+
<tfoot>
|
|
215
|
+
<tr class="subtotal-row">
|
|
216
|
+
<td colspan="2">Summe Aufwendungen</td>
|
|
217
|
+
<td class="number">${this.formatGermanNumber(report.totalExpenses)}</td>
|
|
218
|
+
</tr>
|
|
219
|
+
</tfoot>
|
|
220
|
+
</table>
|
|
221
|
+
|
|
222
|
+
<div class="result-section">
|
|
223
|
+
<h2>Ergebnis</h2>
|
|
224
|
+
<table class="summary-table">
|
|
225
|
+
<tr>
|
|
226
|
+
<td>Erträge</td>
|
|
227
|
+
<td class="number">${this.formatGermanNumber(report.totalRevenue)}</td>
|
|
228
|
+
</tr>
|
|
229
|
+
<tr>
|
|
230
|
+
<td>Aufwendungen</td>
|
|
231
|
+
<td class="number">- ${this.formatGermanNumber(report.totalExpenses)}</td>
|
|
232
|
+
</tr>
|
|
233
|
+
<tr class="total-row">
|
|
234
|
+
<td>${report.netIncome >= 0 ? 'Jahresüberschuss' : 'Jahresfehlbetrag'}</td>
|
|
235
|
+
<td class="number ${report.netIncome >= 0 ? 'positive' : 'negative'}">
|
|
236
|
+
${this.formatGermanNumber(report.netIncome)}
|
|
237
|
+
</td>
|
|
238
|
+
</tr>
|
|
239
|
+
</table>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
${this.generateFooter()}
|
|
243
|
+
</body>
|
|
244
|
+
</html>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generates HTML for balance sheet report
|
|
250
|
+
*/
|
|
251
|
+
private generateBalanceSheetHtml(report: IBalanceSheet): string {
|
|
252
|
+
const assetRows = [...(report.assets.current || []), ...(report.assets.fixed || [])].map(entry => `
|
|
253
|
+
<tr>
|
|
254
|
+
<td>${entry.accountNumber}</td>
|
|
255
|
+
<td>${entry.accountName}</td>
|
|
256
|
+
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
|
|
257
|
+
</tr>
|
|
258
|
+
`).join('');
|
|
259
|
+
|
|
260
|
+
const liabilityRows = [...(report.liabilities.current || []), ...(report.liabilities.longTerm || [])].map(entry => `
|
|
261
|
+
<tr>
|
|
262
|
+
<td>${entry.accountNumber}</td>
|
|
263
|
+
<td>${entry.accountName}</td>
|
|
264
|
+
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
|
|
265
|
+
</tr>
|
|
266
|
+
`).join('');
|
|
267
|
+
|
|
268
|
+
const equityRows = (report.equity.entries || []).map(entry => `
|
|
269
|
+
<tr>
|
|
270
|
+
<td>${entry.accountNumber}</td>
|
|
271
|
+
<td>${entry.accountName}</td>
|
|
272
|
+
<td class="number">${this.formatGermanNumber(entry.amount)}</td>
|
|
273
|
+
</tr>
|
|
274
|
+
`).join('');
|
|
275
|
+
|
|
276
|
+
return `
|
|
277
|
+
<!DOCTYPE html>
|
|
278
|
+
<html>
|
|
279
|
+
<head>
|
|
280
|
+
<meta charset="UTF-8">
|
|
281
|
+
<style>
|
|
282
|
+
${this.getBaseStyles()}
|
|
283
|
+
</style>
|
|
284
|
+
</head>
|
|
285
|
+
<body>
|
|
286
|
+
${this.generateHeader('Bilanz')}
|
|
287
|
+
|
|
288
|
+
<div class="balance-sheet">
|
|
289
|
+
<div class="aktiva">
|
|
290
|
+
<h2>Aktiva</h2>
|
|
291
|
+
<table class="report-table">
|
|
292
|
+
<thead>
|
|
293
|
+
<tr>
|
|
294
|
+
<th>Konto</th>
|
|
295
|
+
<th>Bezeichnung</th>
|
|
296
|
+
<th>Betrag</th>
|
|
297
|
+
</tr>
|
|
298
|
+
</thead>
|
|
299
|
+
<tbody>
|
|
300
|
+
${assetRows}
|
|
301
|
+
</tbody>
|
|
302
|
+
<tfoot>
|
|
303
|
+
<tr class="total-row">
|
|
304
|
+
<td colspan="2">Summe Aktiva</td>
|
|
305
|
+
<td class="number">${this.formatGermanNumber(report.assets.totalAssets)}</td>
|
|
306
|
+
</tr>
|
|
307
|
+
</tfoot>
|
|
308
|
+
</table>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div class="passiva">
|
|
312
|
+
<h2>Passiva</h2>
|
|
313
|
+
|
|
314
|
+
<h3>Eigenkapital</h3>
|
|
315
|
+
<table class="report-table">
|
|
316
|
+
<tbody>
|
|
317
|
+
${equityRows}
|
|
318
|
+
</tbody>
|
|
319
|
+
<tfoot>
|
|
320
|
+
<tr class="subtotal-row">
|
|
321
|
+
<td colspan="2">Summe Eigenkapital</td>
|
|
322
|
+
<td class="number">${this.formatGermanNumber(report.equity.totalEquity)}</td>
|
|
323
|
+
</tr>
|
|
324
|
+
</tfoot>
|
|
325
|
+
</table>
|
|
326
|
+
|
|
327
|
+
<h3>Fremdkapital</h3>
|
|
328
|
+
<table class="report-table">
|
|
329
|
+
<tbody>
|
|
330
|
+
${liabilityRows}
|
|
331
|
+
</tbody>
|
|
332
|
+
<tfoot>
|
|
333
|
+
<tr class="subtotal-row">
|
|
334
|
+
<td colspan="2">Summe Fremdkapital</td>
|
|
335
|
+
<td class="number">${this.formatGermanNumber(report.liabilities.totalLiabilities)}</td>
|
|
336
|
+
</tr>
|
|
337
|
+
</tfoot>
|
|
338
|
+
</table>
|
|
339
|
+
|
|
340
|
+
<table class="summary-table">
|
|
341
|
+
<tr class="total-row">
|
|
342
|
+
<td>Summe Passiva</td>
|
|
343
|
+
<td class="number">${this.formatGermanNumber(report.liabilities.totalLiabilities + report.equity.totalEquity)}</td>
|
|
344
|
+
</tr>
|
|
345
|
+
</table>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
${this.generateFooter()}
|
|
350
|
+
</body>
|
|
351
|
+
</html>
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Generates comprehensive Jahresabschluss HTML
|
|
357
|
+
*/
|
|
358
|
+
private generateJahresabschlussHtml(
|
|
359
|
+
trialBalance: ITrialBalanceReport,
|
|
360
|
+
incomeStatement: IIncomeStatement,
|
|
361
|
+
balanceSheet: IBalanceSheet
|
|
362
|
+
): string {
|
|
363
|
+
return `
|
|
364
|
+
<!DOCTYPE html>
|
|
365
|
+
<html>
|
|
366
|
+
<head>
|
|
367
|
+
<meta charset="UTF-8">
|
|
368
|
+
<style>
|
|
369
|
+
${this.getBaseStyles()}
|
|
370
|
+
.page-break { page-break-after: always; }
|
|
371
|
+
.cover-page {
|
|
372
|
+
display: flex;
|
|
373
|
+
flex-direction: column;
|
|
374
|
+
justify-content: center;
|
|
375
|
+
align-items: center;
|
|
376
|
+
height: 100vh;
|
|
377
|
+
text-align: center;
|
|
378
|
+
}
|
|
379
|
+
.cover-page h1 { font-size: 36px; margin-bottom: 20px; }
|
|
380
|
+
.cover-page h2 { font-size: 24px; margin-bottom: 40px; }
|
|
381
|
+
.toc { margin-top: 50px; }
|
|
382
|
+
.toc h2 { margin-bottom: 20px; }
|
|
383
|
+
.toc ul { list-style: none; padding: 0; }
|
|
384
|
+
.toc li { margin: 10px 0; font-size: 16px; }
|
|
385
|
+
</style>
|
|
386
|
+
</head>
|
|
387
|
+
<body>
|
|
388
|
+
<div class="cover-page">
|
|
389
|
+
<h1>Jahresabschluss</h1>
|
|
390
|
+
<h2>${this.options.companyName}</h2>
|
|
391
|
+
<p>Geschäftsjahr ${this.options.fiscalYear}</p>
|
|
392
|
+
<p>${this.formatGermanDate(this.options.dateFrom)} bis ${this.formatGermanDate(this.options.dateTo)}</p>
|
|
393
|
+
|
|
394
|
+
<div class="toc">
|
|
395
|
+
<h2>Inhalt</h2>
|
|
396
|
+
<ul>
|
|
397
|
+
<li>1. Bilanz</li>
|
|
398
|
+
<li>2. Gewinn- und Verlustrechnung</li>
|
|
399
|
+
<li>3. Summen- und Saldenliste</li>
|
|
400
|
+
</ul>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<div class="page-break"></div>
|
|
405
|
+
${this.generateBalanceSheetHtml(balanceSheet)}
|
|
406
|
+
|
|
407
|
+
<div class="page-break"></div>
|
|
408
|
+
${this.generateIncomeStatementHtml(incomeStatement)}
|
|
409
|
+
|
|
410
|
+
<div class="page-break"></div>
|
|
411
|
+
${this.generateTrialBalanceHtml(trialBalance)}
|
|
412
|
+
</body>
|
|
413
|
+
</html>
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Generates the report header
|
|
419
|
+
*/
|
|
420
|
+
private generateHeader(reportTitle: string): string {
|
|
421
|
+
return `
|
|
422
|
+
<div class="header">
|
|
423
|
+
<h1>${this.options.companyName}</h1>
|
|
424
|
+
${this.options.companyAddress ? `<p>${this.options.companyAddress}</p>` : ''}
|
|
425
|
+
${this.options.taxId ? `<p>Steuernummer: ${this.options.taxId}</p>` : ''}
|
|
426
|
+
${this.options.registrationNumber ? `<p>Handelsregister: ${this.options.registrationNumber}</p>` : ''}
|
|
427
|
+
<hr>
|
|
428
|
+
<h2>${reportTitle}</h2>
|
|
429
|
+
<p>Periode: ${this.formatGermanDate(this.options.dateFrom)} bis ${this.formatGermanDate(this.options.dateTo)}</p>
|
|
430
|
+
</div>
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Generates the report footer
|
|
436
|
+
*/
|
|
437
|
+
private generateFooter(): string {
|
|
438
|
+
const preparedDate = this.options.preparedDate || new Date();
|
|
439
|
+
return `
|
|
440
|
+
<div class="footer">
|
|
441
|
+
<hr>
|
|
442
|
+
<p>Erstellt am: ${this.formatGermanDate(preparedDate)}</p>
|
|
443
|
+
${this.options.preparedBy ? `<p>Erstellt von: ${this.options.preparedBy}</p>` : ''}
|
|
444
|
+
<p class="disclaimer">
|
|
445
|
+
Dieser Bericht wurde automatisch generiert und ist Teil des revisionssicheren
|
|
446
|
+
Jahresabschluss-Exports gemäß GoBD.
|
|
447
|
+
</p>
|
|
448
|
+
</div>
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Gets the base CSS styles for all reports
|
|
454
|
+
*/
|
|
455
|
+
private getBaseStyles(): string {
|
|
456
|
+
return `
|
|
457
|
+
body {
|
|
458
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
459
|
+
margin: 40px;
|
|
460
|
+
color: #333;
|
|
461
|
+
line-height: 1.6;
|
|
462
|
+
}
|
|
463
|
+
h1 { color: #2c3e50; margin-bottom: 10px; }
|
|
464
|
+
h2 { color: #34495e; margin-top: 30px; margin-bottom: 15px; }
|
|
465
|
+
h3 { color: #7f8c8d; margin-top: 20px; margin-bottom: 10px; }
|
|
466
|
+
|
|
467
|
+
.header {
|
|
468
|
+
text-align: center;
|
|
469
|
+
margin-bottom: 40px;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.footer {
|
|
473
|
+
margin-top: 50px;
|
|
474
|
+
text-align: center;
|
|
475
|
+
font-size: 12px;
|
|
476
|
+
color: #7f8c8d;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.disclaimer {
|
|
480
|
+
margin-top: 20px;
|
|
481
|
+
font-style: italic;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
table {
|
|
485
|
+
width: 100%;
|
|
486
|
+
border-collapse: collapse;
|
|
487
|
+
margin: 20px 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
th {
|
|
491
|
+
background-color: #34495e;
|
|
492
|
+
color: white;
|
|
493
|
+
padding: 10px;
|
|
494
|
+
text-align: left;
|
|
495
|
+
font-weight: 600;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
td {
|
|
499
|
+
padding: 8px;
|
|
500
|
+
border-bottom: 1px solid #ecf0f1;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
tbody tr:hover {
|
|
504
|
+
background-color: #f8f9fa;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.number {
|
|
508
|
+
text-align: right;
|
|
509
|
+
font-family: 'Courier New', monospace;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.total-row {
|
|
513
|
+
font-weight: bold;
|
|
514
|
+
background-color: #ecf0f1;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.subtotal-row {
|
|
518
|
+
font-weight: 600;
|
|
519
|
+
background-color: #f8f9fa;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.positive {
|
|
523
|
+
color: #27ae60;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.negative {
|
|
527
|
+
color: #e74c3c;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.result-section {
|
|
531
|
+
margin-top: 40px;
|
|
532
|
+
padding: 20px;
|
|
533
|
+
background-color: #f8f9fa;
|
|
534
|
+
border-radius: 5px;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.summary-table {
|
|
538
|
+
max-width: 500px;
|
|
539
|
+
margin: 20px auto;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.balance-sheet {
|
|
543
|
+
display: flex;
|
|
544
|
+
gap: 40px;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.aktiva, .passiva {
|
|
548
|
+
flex: 1;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
@media print {
|
|
552
|
+
body { margin: 20px; }
|
|
553
|
+
.page-break { page-break-after: always; }
|
|
554
|
+
}
|
|
555
|
+
`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Formats number in German format (1.234,56)
|
|
560
|
+
*/
|
|
561
|
+
private formatGermanNumber(value: number): string {
|
|
562
|
+
return value.toLocaleString('de-DE', {
|
|
563
|
+
minimumFractionDigits: 2,
|
|
564
|
+
maximumFractionDigits: 2
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Formats date in German format (DD.MM.YYYY)
|
|
570
|
+
*/
|
|
571
|
+
private formatGermanDate(date: Date): string {
|
|
572
|
+
return date.toLocaleDateString('de-DE', {
|
|
573
|
+
day: '2-digit',
|
|
574
|
+
month: '2-digit',
|
|
575
|
+
year: 'numeric'
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Saves a PDF report to the export directory
|
|
581
|
+
*/
|
|
582
|
+
public async savePdfReport(filename: string, pdfBuffer: Buffer): Promise<string> {
|
|
583
|
+
const reportsDir = path.join(this.exportPath, 'data', 'reports');
|
|
584
|
+
await plugins.smartfile.fs.ensureDir(reportsDir);
|
|
585
|
+
|
|
586
|
+
const filePath = path.join(reportsDir, filename);
|
|
587
|
+
await plugins.smartfile.memory.toFs(pdfBuffer, filePath);
|
|
588
|
+
|
|
589
|
+
return filePath;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Closes the PDF generator
|
|
594
|
+
*/
|
|
595
|
+
public async close(): Promise<void> {
|
|
596
|
+
if (this.pdfInstance) {
|
|
597
|
+
await this.pdfInstance.stop();
|
|
598
|
+
this.pdfInstance = null;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|