@fin.cx/skr 1.1.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.
Files changed (46) hide show
  1. package/dist_ts/index.d.ts +6 -0
  2. package/dist_ts/index.js +7 -1
  3. package/dist_ts/plugins.d.ts +8 -1
  4. package/dist_ts/plugins.js +10 -2
  5. package/dist_ts/skr.api.d.ts +70 -0
  6. package/dist_ts/skr.api.js +348 -1
  7. package/dist_ts/skr.export.accounts.d.ts +53 -0
  8. package/dist_ts/skr.export.accounts.js +111 -0
  9. package/dist_ts/skr.export.balances.d.ts +59 -0
  10. package/dist_ts/skr.export.balances.js +205 -0
  11. package/dist_ts/skr.export.d.ts +110 -0
  12. package/dist_ts/skr.export.js +315 -0
  13. package/dist_ts/skr.export.ledger.d.ts +95 -0
  14. package/dist_ts/skr.export.ledger.js +164 -0
  15. package/dist_ts/skr.export.pdf.d.ts +82 -0
  16. package/dist_ts/skr.export.pdf.js +548 -0
  17. package/dist_ts/skr.invoice.adapter.d.ts +98 -0
  18. package/dist_ts/skr.invoice.adapter.js +476 -0
  19. package/dist_ts/skr.invoice.booking.d.ts +102 -0
  20. package/dist_ts/skr.invoice.booking.js +556 -0
  21. package/dist_ts/skr.invoice.entity.d.ts +287 -0
  22. package/dist_ts/skr.invoice.entity.js +2 -0
  23. package/dist_ts/skr.invoice.mapper.d.ts +69 -0
  24. package/dist_ts/skr.invoice.mapper.js +401 -0
  25. package/dist_ts/skr.invoice.storage.d.ts +140 -0
  26. package/dist_ts/skr.invoice.storage.js +529 -0
  27. package/dist_ts/skr.security.d.ts +65 -0
  28. package/dist_ts/skr.security.js +319 -0
  29. package/dist_ts/skr.types.d.ts +1 -0
  30. package/package.json +17 -12
  31. package/readme.md +207 -16
  32. package/ts/index.ts +6 -0
  33. package/ts/plugins.ts +22 -1
  34. package/ts/skr.api.ts +485 -0
  35. package/ts/skr.export.accounts.ts +154 -0
  36. package/ts/skr.export.balances.ts +270 -0
  37. package/ts/skr.export.ledger.ts +249 -0
  38. package/ts/skr.export.pdf.ts +601 -0
  39. package/ts/skr.export.ts +443 -0
  40. package/ts/skr.invoice.adapter.ts +581 -0
  41. package/ts/skr.invoice.booking.ts +738 -0
  42. package/ts/skr.invoice.entity.ts +351 -0
  43. package/ts/skr.invoice.mapper.ts +486 -0
  44. package/ts/skr.invoice.storage.ts +710 -0
  45. package/ts/skr.security.ts +405 -0
  46. package/ts/skr.types.ts +1 -0
@@ -0,0 +1,443 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as path from 'path';
3
+ import type { IAccountData, ITransactionData, IJournalEntry, TSKRType } from './skr.types.js';
4
+
5
+ export interface IExportOptions {
6
+ exportPath: string;
7
+ fiscalYear: number;
8
+ dateFrom: Date;
9
+ dateTo: Date;
10
+ includeDocuments?: boolean;
11
+ generatePdfReports?: boolean;
12
+ signExport?: boolean;
13
+ timestampExport?: boolean;
14
+ companyInfo?: {
15
+ name: string;
16
+ taxId: string;
17
+ registrationNumber?: string;
18
+ address?: string;
19
+ };
20
+ }
21
+
22
+ export interface IExportMetadata {
23
+ exportVersion: string;
24
+ exportTimestamp: string;
25
+ generator: {
26
+ name: string;
27
+ version: string;
28
+ };
29
+ company?: {
30
+ name: string;
31
+ taxId: string;
32
+ registrationNumber?: string;
33
+ address?: string;
34
+ };
35
+ fiscalYear: number;
36
+ dateRange: {
37
+ from: string;
38
+ to: string;
39
+ };
40
+ skrType: TSKRType;
41
+ schemaVersion: string;
42
+ crypto: {
43
+ digestAlgorithms: string[];
44
+ signatureType?: string;
45
+ timestampPolicy?: string;
46
+ merkleTree: boolean;
47
+ };
48
+ options: {
49
+ packagedAs: 'bagit';
50
+ compression: 'none' | 'deflate';
51
+ deduplication: boolean;
52
+ };
53
+ }
54
+
55
+ export interface IBagItManifest {
56
+ [filePath: string]: string; // filePath -> SHA256 hash
57
+ }
58
+
59
+ export interface IDocumentIndex {
60
+ contentHash: string;
61
+ sizeBytes: number;
62
+ mimeType: string;
63
+ createdAt: string;
64
+ originalFilename?: string;
65
+ pdfaAvailable: boolean;
66
+ zugferdXml?: string;
67
+ retentionClass: string;
68
+ }
69
+
70
+ export class SkrExport {
71
+ private logger: plugins.smartlog.ConsoleLog;
72
+ private options: IExportOptions;
73
+ private exportDir: string;
74
+ private manifest: IBagItManifest = {};
75
+ private tagManifest: IBagItManifest = {};
76
+
77
+ constructor(options: IExportOptions) {
78
+ this.options = options;
79
+ this.logger = new plugins.smartlog.ConsoleLog();
80
+ this.exportDir = path.join(options.exportPath, `jahresabschluss_${options.fiscalYear}`);
81
+ }
82
+
83
+ /**
84
+ * Creates the BagIt directory structure for the export
85
+ */
86
+ public async createBagItStructure(): Promise<void> {
87
+ this.logger.log('info', 'Creating BagIt directory structure...');
88
+
89
+ // Create main directories
90
+ await plugins.smartfile.fs.ensureDir(this.exportDir);
91
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data'));
92
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'metadata'));
93
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'metadata', 'schemas'));
94
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'metadata', 'schemas', 'v1'));
95
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'metadata', 'signatures'));
96
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'accounting'));
97
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'accounting', 'ebilanz'));
98
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'documents'));
99
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'documents', 'by-hash'));
100
+ await plugins.smartfile.fs.ensureDir(path.join(this.exportDir, 'data', 'reports'));
101
+
102
+ // Create BagIt declaration file
103
+ await this.createBagItDeclaration();
104
+
105
+ // Create README
106
+ await this.createReadme();
107
+
108
+ this.logger.log('ok', 'BagIt structure created successfully');
109
+ }
110
+
111
+ /**
112
+ * Creates the bagit.txt declaration file
113
+ */
114
+ private async createBagItDeclaration(): Promise<void> {
115
+ const bagitContent = `BagIt-Version: 1.0
116
+ Tag-File-Character-Encoding: UTF-8`;
117
+
118
+ const filePath = path.join(this.exportDir, 'bagit.txt');
119
+ await plugins.smartfile.memory.toFs(bagitContent, filePath);
120
+
121
+ // Add to tag manifest
122
+ const hash = await this.hashFile(filePath);
123
+ this.tagManifest['bagit.txt'] = hash;
124
+ }
125
+
126
+ /**
127
+ * Creates the README.txt file with Verfahrensdokumentation
128
+ */
129
+ private async createReadme(): Promise<void> {
130
+ const readmeContent = `SKR Jahresabschluss Export - Verfahrensdokumentation
131
+ =====================================================
132
+
133
+ Dieses Archiv enthält einen revisionssicheren Export des Jahresabschlusses
134
+ gemäß den Grundsätzen ordnungsmäßiger Buchführung (GoBD).
135
+
136
+ Export-Datum: ${new Date().toISOString()}
137
+ Geschäftsjahr: ${this.options.fiscalYear}
138
+ Zeitraum: ${this.options.dateFrom.toISOString()} bis ${this.options.dateTo.toISOString()}
139
+
140
+ STRUKTUR DES ARCHIVS
141
+ --------------------
142
+ - /data/accounting/: Buchhaltungsdaten (Journale, Konten, Salden)
143
+ - /data/documents/: Belegdokumente (content-adressiert)
144
+ - /data/reports/: Finanzberichte (PDF/A-3)
145
+ - /data/metadata/: Export-Metadaten und Schemas
146
+ - /data/metadata/signatures/: Digitale Signaturen und Zeitstempel
147
+
148
+ INTEGRITÄTSSICHERUNG
149
+ --------------------
150
+ - Alle Dateien sind mit SHA-256 gehasht (siehe manifest-sha256.txt)
151
+ - Optional: Digitale Signatur (CAdES) über Manifest
152
+ - Optional: RFC 3161 Zeitstempel
153
+
154
+ AUFBEWAHRUNG
155
+ ------------
156
+ Dieses Archiv muss gemäß § 147 AO für 10 Jahre revisionssicher aufbewahrt werden.
157
+ Empfohlen wird die Speicherung auf WORM-Medien.
158
+
159
+ REIMPORT
160
+ --------
161
+ Das Archiv kann mit der SKR-Software vollständig reimportiert werden.
162
+ Die Datenintegrität wird beim Import automatisch verifiziert.
163
+
164
+ COMPLIANCE
165
+ ----------
166
+ - GoBD-konform
167
+ - E-Bilanz-fähig (XBRL)
168
+ - ZUGFeRD/Factur-X kompatibel
169
+ - PDF/A-3 für Langzeitarchivierung
170
+
171
+ © ${new Date().getFullYear()} ${this.options.companyInfo?.name || 'Export System'}`;
172
+
173
+ const filePath = path.join(this.exportDir, 'readme.txt');
174
+ await plugins.smartfile.memory.toFs(readmeContent, filePath);
175
+
176
+ // Add to tag manifest
177
+ const hash = await this.hashFile(filePath);
178
+ this.tagManifest['readme.txt'] = hash;
179
+ }
180
+
181
+ /**
182
+ * Creates the export metadata JSON file
183
+ */
184
+ public async createExportMetadata(skrType: TSKRType): Promise<void> {
185
+ const metadata: IExportMetadata = {
186
+ exportVersion: '1.0.0',
187
+ exportTimestamp: new Date().toISOString(),
188
+ generator: {
189
+ name: '@fin.cx/skr',
190
+ version: '1.1.0' // Should be read from package.json
191
+ },
192
+ company: this.options.companyInfo,
193
+ fiscalYear: this.options.fiscalYear,
194
+ dateRange: {
195
+ from: this.options.dateFrom.toISOString(),
196
+ to: this.options.dateTo.toISOString()
197
+ },
198
+ skrType: skrType,
199
+ schemaVersion: '1.0',
200
+ crypto: {
201
+ digestAlgorithms: ['sha256'],
202
+ signatureType: this.options.signExport ? 'CAdES' : undefined,
203
+ timestampPolicy: this.options.timestampExport ? 'RFC3161' : undefined,
204
+ merkleTree: true
205
+ },
206
+ options: {
207
+ packagedAs: 'bagit',
208
+ compression: 'none',
209
+ deduplication: true
210
+ }
211
+ };
212
+
213
+ const filePath = path.join(this.exportDir, 'data', 'metadata', 'export.json');
214
+ await plugins.smartfile.memory.toFs(JSON.stringify(metadata, null, 2), filePath);
215
+
216
+ // Add to manifest
217
+ const hash = await this.hashFile(filePath);
218
+ this.manifest['data/metadata/export.json'] = hash;
219
+ }
220
+
221
+ /**
222
+ * Creates JSON schemas for the export data structures
223
+ */
224
+ public async createSchemas(): Promise<void> {
225
+ // Ledger schema
226
+ const ledgerSchema = {
227
+ "$schema": "http://json-schema.org/draft-07/schema#",
228
+ "title": "Ledger Entry",
229
+ "type": "object",
230
+ "properties": {
231
+ "schema_version": { "type": "string" },
232
+ "entry_id": { "type": "string", "format": "uuid" },
233
+ "booking_date": { "type": "string", "format": "date" },
234
+ "posting_date": { "type": "string", "format": "date" },
235
+ "currency": { "type": "string" },
236
+ "journal": { "type": "string" },
237
+ "description": { "type": "string" },
238
+ "lines": {
239
+ "type": "array",
240
+ "items": {
241
+ "type": "object",
242
+ "properties": {
243
+ "posting_id": { "type": "string" },
244
+ "account_code": { "type": "string" },
245
+ "debit": { "type": "string" },
246
+ "credit": { "type": "string" },
247
+ "tax_code": { "type": "string" },
248
+ "document_refs": {
249
+ "type": "array",
250
+ "items": {
251
+ "type": "object",
252
+ "properties": {
253
+ "content_hash": { "type": "string" },
254
+ "doc_role": { "type": "string" },
255
+ "mime": { "type": "string" }
256
+ }
257
+ }
258
+ }
259
+ },
260
+ "required": ["posting_id", "account_code", "debit", "credit"]
261
+ }
262
+ },
263
+ "created_at": { "type": "string", "format": "date-time" },
264
+ "user": { "type": "string" }
265
+ },
266
+ "required": ["schema_version", "entry_id", "booking_date", "lines"]
267
+ };
268
+
269
+ // Accounts schema
270
+ const accountsSchema = {
271
+ "$schema": "http://json-schema.org/draft-07/schema#",
272
+ "title": "Accounts CSV",
273
+ "type": "object",
274
+ "properties": {
275
+ "account_code": { "type": "string" },
276
+ "name": { "type": "string" },
277
+ "type": { "type": "string" },
278
+ "parent": { "type": "string" },
279
+ "skr_set": { "type": "string" },
280
+ "tax_code_default": { "type": "string" },
281
+ "active_from": { "type": "string", "format": "date" },
282
+ "active_to": { "type": "string", "format": "date" }
283
+ },
284
+ "required": ["account_code", "name", "type", "skr_set"]
285
+ };
286
+
287
+ // Save schemas
288
+ const schemasDir = path.join(this.exportDir, 'data', 'metadata', 'schemas', 'v1');
289
+
290
+ await plugins.smartfile.memory.toFs(
291
+ JSON.stringify(ledgerSchema, null, 2),
292
+ path.join(schemasDir, 'ledger.schema.json')
293
+ );
294
+ this.manifest['data/metadata/schemas/v1/ledger.schema.json'] = await this.hashFile(
295
+ path.join(schemasDir, 'ledger.schema.json')
296
+ );
297
+
298
+ await plugins.smartfile.memory.toFs(
299
+ JSON.stringify(accountsSchema, null, 2),
300
+ path.join(schemasDir, 'accounts.schema.json')
301
+ );
302
+ this.manifest['data/metadata/schemas/v1/accounts.schema.json'] = await this.hashFile(
303
+ path.join(schemasDir, 'accounts.schema.json')
304
+ );
305
+ }
306
+
307
+ /**
308
+ * Writes the BagIt manifest files
309
+ */
310
+ public async writeManifests(): Promise<void> {
311
+ // Write data manifest
312
+ let manifestContent = '';
313
+ for (const [filePath, hash] of Object.entries(this.manifest)) {
314
+ manifestContent += `${hash} ${filePath}\n`;
315
+ }
316
+
317
+ const manifestPath = path.join(this.exportDir, 'manifest-sha256.txt');
318
+ await plugins.smartfile.memory.toFs(manifestContent, manifestPath);
319
+
320
+ // Add manifest to tag manifest
321
+ this.tagManifest['manifest-sha256.txt'] = await this.hashFile(manifestPath);
322
+
323
+ // Write tag manifest
324
+ let tagManifestContent = '';
325
+ for (const [filePath, hash] of Object.entries(this.tagManifest)) {
326
+ tagManifestContent += `${hash} ${filePath}\n`;
327
+ }
328
+
329
+ await plugins.smartfile.memory.toFs(
330
+ tagManifestContent,
331
+ path.join(this.exportDir, 'tagmanifest-sha256.txt')
332
+ );
333
+ }
334
+
335
+ /**
336
+ * Calculates SHA-256 hash of a file
337
+ */
338
+ private async hashFile(filePath: string): Promise<string> {
339
+ const fileContent = await plugins.smartfile.fs.toBuffer(filePath);
340
+ return await plugins.smarthash.sha256FromBuffer(fileContent);
341
+ }
342
+
343
+ /**
344
+ * Stores a document in content-addressed storage
345
+ */
346
+ public async storeDocument(content: Buffer, originalFilename?: string): Promise<string> {
347
+ const hash = await plugins.smarthash.sha256FromBuffer(content);
348
+
349
+ // Create path based on hash (first 2 chars as directory)
350
+ const hashPrefix = hash.substring(0, 2);
351
+ const hashDir = path.join(this.exportDir, 'data', 'documents', 'by-hash', hashPrefix);
352
+ await plugins.smartfile.fs.ensureDir(hashDir);
353
+
354
+ const docPath = path.join(hashDir, hash);
355
+
356
+ // Only store if not already exists (deduplication)
357
+ if (!(await plugins.smartfile.fs.fileExists(docPath))) {
358
+ await plugins.smartfile.memory.toFs(content, docPath);
359
+ this.manifest[`data/documents/by-hash/${hashPrefix}/${hash}`] = hash;
360
+ }
361
+
362
+ return hash;
363
+ }
364
+
365
+ /**
366
+ * Creates a Merkle tree from all file hashes
367
+ */
368
+ public async createMerkleTree(): Promise<string> {
369
+ const leaves = Object.values(this.manifest).map(hash =>
370
+ Buffer.from(hash, 'hex')
371
+ );
372
+
373
+ // Create a sync hash function wrapper for MerkleTree
374
+ const hashFn = (data: Buffer) => {
375
+ // Convert async to sync by using crypto directly
376
+ const crypto = require('crypto');
377
+ return crypto.createHash('sha256').update(data).digest();
378
+ };
379
+
380
+ const tree = new plugins.MerkleTree(leaves, hashFn, {
381
+ sortPairs: true
382
+ });
383
+
384
+ const root = tree.getRoot().toString('hex');
385
+
386
+ // Save Merkle tree data
387
+ const merkleData = {
388
+ root: root,
389
+ leaves: Object.entries(this.manifest).map(([path, hash]) => ({
390
+ path,
391
+ hash
392
+ })),
393
+ timestamp: new Date().toISOString()
394
+ };
395
+
396
+ const merklePath = path.join(this.exportDir, 'data', 'metadata', 'merkle-tree.json');
397
+ await plugins.smartfile.memory.toFs(JSON.stringify(merkleData, null, 2), merklePath);
398
+ this.manifest['data/metadata/merkle-tree.json'] = await this.hashFile(merklePath);
399
+
400
+ return root;
401
+ }
402
+
403
+ /**
404
+ * Validates the BagIt structure
405
+ */
406
+ public async validateBagIt(): Promise<boolean> {
407
+ this.logger.log('info', 'Validating BagIt structure...');
408
+
409
+ // Check required files exist
410
+ const requiredFiles = [
411
+ 'bagit.txt',
412
+ 'manifest-sha256.txt',
413
+ 'tagmanifest-sha256.txt',
414
+ 'readme.txt'
415
+ ];
416
+
417
+ for (const file of requiredFiles) {
418
+ const filePath = path.join(this.exportDir, file);
419
+ if (!(await plugins.smartfile.fs.fileExists(filePath))) {
420
+ this.logger.log('error', `Required file missing: ${file}`);
421
+ return false;
422
+ }
423
+ }
424
+
425
+ // Verify all manifest entries
426
+ for (const [relPath, expectedHash] of Object.entries(this.manifest)) {
427
+ const fullPath = path.join(this.exportDir, relPath);
428
+ if (!(await plugins.smartfile.fs.fileExists(fullPath))) {
429
+ this.logger.log('error', `Manifest file missing: ${relPath}`);
430
+ return false;
431
+ }
432
+
433
+ const actualHash = await this.hashFile(fullPath);
434
+ if (actualHash !== expectedHash) {
435
+ this.logger.log('error', `Hash mismatch for ${relPath}`);
436
+ return false;
437
+ }
438
+ }
439
+
440
+ this.logger.log('ok', 'BagIt validation successful');
441
+ return true;
442
+ }
443
+ }