@fin.cx/skr 1.2.0 → 1.2.2
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/{npmextra.json → .smartconfig.json} +12 -6
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/index.d.ts +21 -15
- package/dist_ts/index.js +16 -16
- package/dist_ts/plugins.d.ts +15 -5
- package/dist_ts/plugins.js +42 -6
- package/dist_ts/skr.api.js +20 -13
- package/dist_ts/skr.classes.account.d.ts +36 -3
- package/dist_ts/skr.classes.account.js +366 -210
- package/dist_ts/skr.classes.chartofaccounts.d.ts +1 -1
- package/dist_ts/skr.classes.chartofaccounts.js +13 -6
- package/dist_ts/skr.classes.journalentry.d.ts +9 -4
- package/dist_ts/skr.classes.journalentry.js +430 -309
- package/dist_ts/skr.classes.ledger.js +7 -1
- package/dist_ts/skr.classes.reports.js +4 -1
- package/dist_ts/skr.classes.transaction.d.ts +9 -4
- package/dist_ts/skr.classes.transaction.js +315 -244
- package/dist_ts/skr.export.accounts.js +3 -2
- package/dist_ts/skr.export.balances.js +4 -2
- package/dist_ts/skr.export.js +6 -3
- package/dist_ts/skr.export.ledger.js +4 -3
- package/dist_ts/skr.export.pdf.js +6 -3
- package/dist_ts/skr.invoice.adapter.d.ts +2 -0
- package/dist_ts/skr.invoice.adapter.js +22 -10
- package/dist_ts/skr.invoice.booking.js +52 -25
- package/dist_ts/skr.invoice.mapper.js +84 -82
- package/dist_ts/skr.invoice.storage.js +10 -5
- package/dist_ts/skr.postingkeys.d.ts +56 -0
- package/dist_ts/skr.postingkeys.js +196 -0
- package/dist_ts/skr.security.js +24 -21
- package/dist_ts/skr.types.d.ts +18 -0
- package/dist_ts/skr03.data.js +3 -1
- package/dist_ts/skr04.data.js +3 -1
- package/license.md +21 -0
- package/package.json +31 -23
- package/readme.hints.md +54 -1
- package/readme.md +215 -632
- package/readme.plan.md +1 -1
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/index.ts +43 -15
- package/ts/plugins.ts +52 -15
- package/ts/skr.api.ts +9 -5
- package/ts/skr.classes.account.ts +144 -23
- package/ts/skr.classes.chartofaccounts.ts +8 -3
- package/ts/skr.classes.journalentry.ts +117 -24
- package/ts/skr.classes.ledger.ts +4 -0
- package/ts/skr.classes.reports.ts +22 -2
- package/ts/skr.classes.transaction.ts +40 -22
- package/ts/skr.export.pdf.ts +4 -3
- package/ts/skr.invoice.adapter.ts +31 -11
- package/ts/skr.invoice.booking.ts +54 -30
- package/ts/skr.invoice.storage.ts +3 -2
- package/ts/skr.postingkeys.ts +252 -0
- package/ts/skr.security.ts +23 -22
- package/ts/skr.types.ts +26 -0
- package/ts/skr03.data.ts +2 -0
- package/ts/skr04.data.ts +2 -0
|
@@ -2,73 +2,96 @@ import * as plugins from './plugins.js';
|
|
|
2
2
|
import { getDbSync } from './skr.database.js';
|
|
3
3
|
import { Account } from './skr.classes.account.js';
|
|
4
4
|
import { Transaction } from './skr.classes.transaction.js';
|
|
5
|
+
import {
|
|
6
|
+
validatePostingKey,
|
|
7
|
+
validatePostingKeyConsistency,
|
|
8
|
+
getPostingKeyDescription,
|
|
9
|
+
} from './skr.postingkeys.js';
|
|
5
10
|
import type {
|
|
6
11
|
TSKRType,
|
|
7
12
|
IJournalEntry,
|
|
8
13
|
IJournalEntryLine,
|
|
9
14
|
} from './skr.types.js';
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
declare abstract class SmartDataDbDocBase {
|
|
17
|
+
public save(): Promise<void>;
|
|
18
|
+
public delete(): Promise<void>;
|
|
19
|
+
public static getInstance<T>(
|
|
20
|
+
this: new (...args: any[]) => T,
|
|
21
|
+
query: Record<string, any>,
|
|
22
|
+
): Promise<T | null>;
|
|
23
|
+
public static getInstances<T>(
|
|
24
|
+
this: new (...args: any[]) => T,
|
|
25
|
+
query: Record<string, any>,
|
|
26
|
+
): Promise<T[]>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const SmartDataDbDoc = plugins.smartdata.SmartDataDbDoc as unknown as typeof SmartDataDbDocBase;
|
|
30
|
+
const Collection = plugins.smartdata.Collection as any;
|
|
31
|
+
const svDb = plugins.smartdata.svDb as any;
|
|
32
|
+
const unI = plugins.smartdata.unI as any;
|
|
33
|
+
const index = plugins.smartdata.index as any;
|
|
34
|
+
const searchable = plugins.smartdata.searchable as any;
|
|
12
35
|
|
|
13
|
-
@
|
|
14
|
-
export class JournalEntry extends SmartDataDbDoc
|
|
36
|
+
@Collection(() => getDbSync())
|
|
37
|
+
export class JournalEntry extends SmartDataDbDoc {
|
|
15
38
|
@unI()
|
|
16
|
-
public id
|
|
39
|
+
public id!: string;
|
|
17
40
|
|
|
18
41
|
@svDb()
|
|
19
42
|
@index()
|
|
20
|
-
public journalNumber
|
|
43
|
+
public journalNumber!: string;
|
|
21
44
|
|
|
22
45
|
@svDb()
|
|
23
46
|
@index()
|
|
24
|
-
public date
|
|
47
|
+
public date!: Date;
|
|
25
48
|
|
|
26
49
|
@svDb()
|
|
27
50
|
@searchable()
|
|
28
|
-
public description
|
|
51
|
+
public description!: string;
|
|
29
52
|
|
|
30
53
|
@svDb()
|
|
31
54
|
@index()
|
|
32
|
-
public reference
|
|
55
|
+
public reference!: string;
|
|
33
56
|
|
|
34
57
|
@svDb()
|
|
35
|
-
public lines
|
|
58
|
+
public lines!: IJournalEntryLine[];
|
|
36
59
|
|
|
37
60
|
@svDb()
|
|
38
61
|
@index()
|
|
39
|
-
public skrType
|
|
62
|
+
public skrType!: TSKRType;
|
|
40
63
|
|
|
41
64
|
@svDb()
|
|
42
|
-
public totalDebits
|
|
65
|
+
public totalDebits!: number;
|
|
43
66
|
|
|
44
67
|
@svDb()
|
|
45
|
-
public totalCredits
|
|
68
|
+
public totalCredits!: number;
|
|
46
69
|
|
|
47
70
|
@svDb()
|
|
48
|
-
public isBalanced
|
|
71
|
+
public isBalanced!: boolean;
|
|
49
72
|
|
|
50
73
|
@svDb()
|
|
51
74
|
@index()
|
|
52
|
-
public status
|
|
75
|
+
public status!: 'draft' | 'posted' | 'reversed';
|
|
53
76
|
|
|
54
77
|
@svDb()
|
|
55
|
-
public transactionIds
|
|
78
|
+
public transactionIds!: string[];
|
|
56
79
|
|
|
57
80
|
@svDb()
|
|
58
81
|
@index()
|
|
59
|
-
public period
|
|
82
|
+
public period!: string;
|
|
60
83
|
|
|
61
84
|
@svDb()
|
|
62
|
-
public fiscalYear
|
|
85
|
+
public fiscalYear!: number;
|
|
63
86
|
|
|
64
87
|
@svDb()
|
|
65
|
-
public createdAt
|
|
88
|
+
public createdAt!: Date;
|
|
66
89
|
|
|
67
90
|
@svDb()
|
|
68
|
-
public postedAt
|
|
91
|
+
public postedAt!: Date | null;
|
|
69
92
|
|
|
70
93
|
@svDb()
|
|
71
|
-
public createdBy
|
|
94
|
+
public createdBy!: string;
|
|
72
95
|
|
|
73
96
|
constructor(data?: Partial<IJournalEntry>) {
|
|
74
97
|
super();
|
|
@@ -212,22 +235,91 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
|
|
|
212
235
|
throw new Error('Journal entry must have at least 2 lines');
|
|
213
236
|
}
|
|
214
237
|
|
|
215
|
-
// Validate all accounts exist and
|
|
238
|
+
// Validate all accounts exist, are active, and can be posted to
|
|
239
|
+
const validationErrors: string[] = [];
|
|
240
|
+
const validationWarnings: string[] = [];
|
|
241
|
+
|
|
242
|
+
// Check if this journal entry has VAT lines (for smarter posting key validation)
|
|
243
|
+
const hasVATLines = this.lines.some(line =>
|
|
244
|
+
line.accountNumber === '1571' || line.accountNumber === '1771' || line.accountNumber === '1576'
|
|
245
|
+
);
|
|
246
|
+
|
|
216
247
|
for (const line of this.lines) {
|
|
248
|
+
// Validate posting key is present (REQUIRED)
|
|
249
|
+
if (!line.postingKey) {
|
|
250
|
+
validationErrors.push(
|
|
251
|
+
`Line for account ${line.accountNumber} is missing required posting key (Buchungsschlüssel). ` +
|
|
252
|
+
`Posting keys are mandatory for DATEV compliance.`
|
|
253
|
+
);
|
|
254
|
+
continue; // Skip further validation for this line
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Validate account is not an automatic account (Automatikkonto)
|
|
258
|
+
try {
|
|
259
|
+
await Account.validateAccountForPosting(line.accountNumber, this.skrType);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
validationErrors.push(error instanceof Error ? error.message : String(error));
|
|
262
|
+
continue; // Skip further validation for this line
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Get account for posting key validation
|
|
217
266
|
const account = await Account.getAccountByNumber(
|
|
218
267
|
line.accountNumber,
|
|
219
268
|
this.skrType,
|
|
220
269
|
);
|
|
221
270
|
|
|
222
271
|
if (!account) {
|
|
223
|
-
|
|
272
|
+
validationErrors.push(
|
|
224
273
|
`Account ${line.accountNumber} not found for ${this.skrType}`,
|
|
225
274
|
);
|
|
275
|
+
continue;
|
|
226
276
|
}
|
|
227
277
|
|
|
228
278
|
if (!account.isActive) {
|
|
229
|
-
|
|
279
|
+
validationErrors.push(`Account ${line.accountNumber} is not active`);
|
|
280
|
+
continue;
|
|
230
281
|
}
|
|
282
|
+
|
|
283
|
+
// Validate posting key for this line
|
|
284
|
+
const amount = line.debit || line.credit || 0;
|
|
285
|
+
// For journal entries with VAT lines, pass amount as vatAmount to satisfy validation
|
|
286
|
+
const postingKeyValidation = validatePostingKey(
|
|
287
|
+
line.postingKey,
|
|
288
|
+
line.accountNumber,
|
|
289
|
+
amount,
|
|
290
|
+
hasVATLines ? amount : undefined // If entry has VAT lines, we consider the validation satisfied
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (!postingKeyValidation.isValid) {
|
|
294
|
+
validationErrors.push(...postingKeyValidation.errors);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (postingKeyValidation.warnings.length > 0) {
|
|
298
|
+
validationWarnings.push(...postingKeyValidation.warnings);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Validate posting key consistency across all lines
|
|
303
|
+
const consistencyValidation = validatePostingKeyConsistency(this.lines);
|
|
304
|
+
if (!consistencyValidation.isValid) {
|
|
305
|
+
validationErrors.push(...consistencyValidation.errors);
|
|
306
|
+
}
|
|
307
|
+
if (consistencyValidation.warnings.length > 0) {
|
|
308
|
+
validationWarnings.push(...consistencyValidation.warnings);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Log warnings but don't fail validation
|
|
312
|
+
if (validationWarnings.length > 0) {
|
|
313
|
+
console.warn('Journal entry validation warnings:');
|
|
314
|
+
validationWarnings.forEach(warning => console.warn(` - ${warning}`));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Throw if any errors
|
|
318
|
+
if (validationErrors.length > 0) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
'Journal entry validation failed:\n' +
|
|
321
|
+
validationErrors.map(e => ` - ${e}`).join('\n')
|
|
322
|
+
);
|
|
231
323
|
}
|
|
232
324
|
}
|
|
233
325
|
|
|
@@ -255,7 +347,7 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
|
|
|
255
347
|
date: this.date,
|
|
256
348
|
debitAccount: debitLines[0].accountNumber,
|
|
257
349
|
creditAccount: creditLines[0].accountNumber,
|
|
258
|
-
amount: debitLines[0].debit,
|
|
350
|
+
amount: debitLines[0].debit || 0,
|
|
259
351
|
description: this.description,
|
|
260
352
|
reference: this.reference,
|
|
261
353
|
skrType: this.skrType,
|
|
@@ -325,6 +417,7 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
|
|
|
325
417
|
credit: line.debit, // Swap
|
|
326
418
|
description: `Reversal: ${line.description || ''}`,
|
|
327
419
|
costCenter: line.costCenter,
|
|
420
|
+
postingKey: line.postingKey, // Keep same posting key for reversal
|
|
328
421
|
}));
|
|
329
422
|
|
|
330
423
|
const reversalEntry = new JournalEntry({
|
package/ts/skr.classes.ledger.ts
CHANGED
|
@@ -418,6 +418,7 @@ export class Ledger {
|
|
|
418
418
|
accountNumber: account.accountNumber,
|
|
419
419
|
debit: Math.abs(balance),
|
|
420
420
|
description: `Closing ${account.accountName}`,
|
|
421
|
+
postingKey: 40, // Tax-free - internal closing entry
|
|
421
422
|
});
|
|
422
423
|
totalRevenue += Math.abs(balance);
|
|
423
424
|
}
|
|
@@ -429,6 +430,7 @@ export class Ledger {
|
|
|
429
430
|
accountNumber: closingAccountNumber,
|
|
430
431
|
credit: totalRevenue,
|
|
431
432
|
description: 'Revenue closing to P&L',
|
|
433
|
+
postingKey: 40, // Tax-free - internal closing entry
|
|
432
434
|
});
|
|
433
435
|
|
|
434
436
|
const revenueClosingEntry = await this.postJournalEntry({
|
|
@@ -458,6 +460,7 @@ export class Ledger {
|
|
|
458
460
|
accountNumber: account.accountNumber,
|
|
459
461
|
credit: Math.abs(balance),
|
|
460
462
|
description: `Closing ${account.accountName}`,
|
|
463
|
+
postingKey: 40, // Tax-free - internal closing entry
|
|
461
464
|
});
|
|
462
465
|
totalExpense += Math.abs(balance);
|
|
463
466
|
}
|
|
@@ -469,6 +472,7 @@ export class Ledger {
|
|
|
469
472
|
accountNumber: closingAccountNumber,
|
|
470
473
|
debit: totalExpense,
|
|
471
474
|
description: 'Expense closing to P&L',
|
|
475
|
+
postingKey: 40, // Tax-free - internal closing entry
|
|
472
476
|
});
|
|
473
477
|
|
|
474
478
|
const expenseClosingEntry = await this.postJournalEntry({
|
|
@@ -410,7 +410,20 @@ export class Reports {
|
|
|
410
410
|
isActive: true,
|
|
411
411
|
});
|
|
412
412
|
|
|
413
|
-
const ledgerEntries
|
|
413
|
+
const ledgerEntries: Array<{
|
|
414
|
+
accountNumber: string;
|
|
415
|
+
accountName: string;
|
|
416
|
+
accountType: string;
|
|
417
|
+
entries: Array<{
|
|
418
|
+
date: Date;
|
|
419
|
+
reference: string;
|
|
420
|
+
description: string;
|
|
421
|
+
debit: number;
|
|
422
|
+
credit: number;
|
|
423
|
+
balance: number;
|
|
424
|
+
}>;
|
|
425
|
+
finalBalance: number;
|
|
426
|
+
}> = [];
|
|
414
427
|
|
|
415
428
|
for (const account of accounts) {
|
|
416
429
|
const transactions = await this.getAccountTransactions(
|
|
@@ -420,7 +433,14 @@ export class Reports {
|
|
|
420
433
|
|
|
421
434
|
if (transactions.length > 0) {
|
|
422
435
|
let runningBalance = 0;
|
|
423
|
-
const accountEntries
|
|
436
|
+
const accountEntries: Array<{
|
|
437
|
+
date: Date;
|
|
438
|
+
reference: string;
|
|
439
|
+
description: string;
|
|
440
|
+
debit: number;
|
|
441
|
+
credit: number;
|
|
442
|
+
balance: number;
|
|
443
|
+
}> = [];
|
|
424
444
|
|
|
425
445
|
for (const transaction of transactions) {
|
|
426
446
|
const isDebit = transaction.debitAccount === account.accountNumber;
|
|
@@ -7,75 +7,93 @@ import type {
|
|
|
7
7
|
ITransactionData,
|
|
8
8
|
} from './skr.types.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
declare abstract class SmartDataDbDocBase {
|
|
11
|
+
public save(): Promise<void>;
|
|
12
|
+
public delete(): Promise<void>;
|
|
13
|
+
public static getInstance<T>(
|
|
14
|
+
this: new (...args: any[]) => T,
|
|
15
|
+
query: Record<string, any>,
|
|
16
|
+
): Promise<T | null>;
|
|
17
|
+
public static getInstances<T>(
|
|
18
|
+
this: new (...args: any[]) => T,
|
|
19
|
+
query: Record<string, any>,
|
|
20
|
+
): Promise<T[]>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SmartDataDbDoc = plugins.smartdata.SmartDataDbDoc as unknown as typeof SmartDataDbDocBase;
|
|
24
|
+
const Collection = plugins.smartdata.Collection as any;
|
|
25
|
+
const svDb = plugins.smartdata.svDb as any;
|
|
26
|
+
const unI = plugins.smartdata.unI as any;
|
|
27
|
+
const index = plugins.smartdata.index as any;
|
|
28
|
+
const searchable = plugins.smartdata.searchable as any;
|
|
11
29
|
|
|
12
|
-
@
|
|
13
|
-
export class Transaction extends SmartDataDbDoc
|
|
30
|
+
@Collection(() => getDbSync())
|
|
31
|
+
export class Transaction extends SmartDataDbDoc {
|
|
14
32
|
@unI()
|
|
15
|
-
public id
|
|
33
|
+
public id!: string;
|
|
16
34
|
|
|
17
35
|
@svDb()
|
|
18
36
|
@index()
|
|
19
|
-
public transactionNumber
|
|
37
|
+
public transactionNumber!: string;
|
|
20
38
|
|
|
21
39
|
@svDb()
|
|
22
40
|
@index()
|
|
23
|
-
public date
|
|
41
|
+
public date!: Date;
|
|
24
42
|
|
|
25
43
|
@svDb()
|
|
26
44
|
@index()
|
|
27
|
-
public debitAccount
|
|
45
|
+
public debitAccount!: string;
|
|
28
46
|
|
|
29
47
|
@svDb()
|
|
30
48
|
@index()
|
|
31
|
-
public creditAccount
|
|
49
|
+
public creditAccount!: string;
|
|
32
50
|
|
|
33
51
|
@svDb()
|
|
34
|
-
public amount
|
|
52
|
+
public amount!: number;
|
|
35
53
|
|
|
36
54
|
@svDb()
|
|
37
55
|
@searchable()
|
|
38
|
-
public description
|
|
56
|
+
public description!: string;
|
|
39
57
|
|
|
40
58
|
@svDb()
|
|
41
59
|
@index()
|
|
42
|
-
public reference
|
|
60
|
+
public reference!: string;
|
|
43
61
|
|
|
44
62
|
@svDb()
|
|
45
63
|
@index()
|
|
46
|
-
public skrType
|
|
64
|
+
public skrType!: TSKRType;
|
|
47
65
|
|
|
48
66
|
@svDb()
|
|
49
|
-
public vatAmount
|
|
67
|
+
public vatAmount!: number;
|
|
50
68
|
|
|
51
69
|
@svDb()
|
|
52
|
-
public costCenter
|
|
70
|
+
public costCenter!: string;
|
|
53
71
|
|
|
54
72
|
@svDb()
|
|
55
73
|
@index()
|
|
56
|
-
public status
|
|
74
|
+
public status!: TTransactionStatus;
|
|
57
75
|
|
|
58
76
|
@svDb()
|
|
59
|
-
public reversalOf
|
|
77
|
+
public reversalOf!: string;
|
|
60
78
|
|
|
61
79
|
@svDb()
|
|
62
|
-
public reversedBy
|
|
80
|
+
public reversedBy!: string;
|
|
63
81
|
|
|
64
82
|
@svDb()
|
|
65
83
|
@index()
|
|
66
|
-
public period
|
|
84
|
+
public period!: string; // Format: YYYY-MM
|
|
67
85
|
|
|
68
86
|
@svDb()
|
|
69
|
-
public fiscalYear
|
|
87
|
+
public fiscalYear!: number;
|
|
70
88
|
|
|
71
89
|
@svDb()
|
|
72
|
-
public createdAt
|
|
90
|
+
public createdAt!: Date;
|
|
73
91
|
|
|
74
92
|
@svDb()
|
|
75
|
-
public postedAt
|
|
93
|
+
public postedAt!: Date | null;
|
|
76
94
|
|
|
77
95
|
@svDb()
|
|
78
|
-
public createdBy
|
|
96
|
+
public createdBy!: string;
|
|
79
97
|
|
|
80
98
|
constructor(data?: Partial<ITransactionData>) {
|
|
81
99
|
super();
|
package/ts/skr.export.pdf.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import { SmartPdf } from '@push.rocks/smartpdf';
|
|
3
4
|
import type { ITrialBalanceReport, IIncomeStatement, IBalanceSheet } from './skr.types.js';
|
|
4
5
|
|
|
5
6
|
export interface IPdfReportOptions {
|
|
@@ -17,7 +18,7 @@ export interface IPdfReportOptions {
|
|
|
17
18
|
export class PdfReportGenerator {
|
|
18
19
|
private exportPath: string;
|
|
19
20
|
private options: IPdfReportOptions;
|
|
20
|
-
private pdfInstance:
|
|
21
|
+
private pdfInstance: SmartPdf | null = null;
|
|
21
22
|
|
|
22
23
|
constructor(exportPath: string, options: IPdfReportOptions) {
|
|
23
24
|
this.exportPath = exportPath;
|
|
@@ -28,7 +29,7 @@ export class PdfReportGenerator {
|
|
|
28
29
|
* Initializes the PDF generator
|
|
29
30
|
*/
|
|
30
31
|
public async initialize(): Promise<void> {
|
|
31
|
-
this.pdfInstance = new
|
|
32
|
+
this.pdfInstance = new SmartPdf();
|
|
32
33
|
await this.pdfInstance.start();
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -598,4 +599,4 @@ export class PdfReportGenerator {
|
|
|
598
599
|
this.pdfInstance = null;
|
|
599
600
|
}
|
|
600
601
|
}
|
|
601
|
-
}
|
|
602
|
+
}
|
|
@@ -18,11 +18,25 @@ import type {
|
|
|
18
18
|
*/
|
|
19
19
|
export class InvoiceAdapter {
|
|
20
20
|
private logger: plugins.smartlog.ConsoleLog;
|
|
21
|
+
private readonly einvoiceModuleName = '@fin.cx/einvoice';
|
|
21
22
|
|
|
22
23
|
constructor() {
|
|
23
24
|
this.logger = new plugins.smartlog.ConsoleLog();
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
private async getEInvoiceClass(): Promise<{
|
|
28
|
+
new (): any;
|
|
29
|
+
fromXml(xmlString: string): Promise<any>;
|
|
30
|
+
}> {
|
|
31
|
+
const { EInvoice } = (await import(this.einvoiceModuleName)) as {
|
|
32
|
+
EInvoice: {
|
|
33
|
+
new (): any;
|
|
34
|
+
fromXml(xmlString: string): Promise<any>;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
return EInvoice;
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
private readonly MAX_XML_SIZE = 10 * 1024 * 1024; // 10MB max
|
|
27
41
|
private readonly MAX_PDF_SIZE = 50 * 1024 * 1024; // 50MB max
|
|
28
42
|
|
|
@@ -44,13 +58,14 @@ export class InvoiceAdapter {
|
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
// Parse the invoice using @fin.cx/einvoice
|
|
47
|
-
|
|
61
|
+
const EInvoice = await this.getEInvoiceClass();
|
|
62
|
+
let einvoice: any;
|
|
48
63
|
if (typeof file === 'string') {
|
|
49
|
-
einvoice = await
|
|
64
|
+
einvoice = await EInvoice.fromXml(file);
|
|
50
65
|
} else {
|
|
51
66
|
// Convert buffer to string first
|
|
52
67
|
const xmlString = file.toString('utf-8');
|
|
53
|
-
einvoice = await
|
|
68
|
+
einvoice = await EInvoice.fromXml(xmlString);
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
// Get detected format
|
|
@@ -74,7 +89,7 @@ export class InvoiceAdapter {
|
|
|
74
89
|
invoice.xmlContent = einvoice.getXml();
|
|
75
90
|
|
|
76
91
|
// Calculate content hash
|
|
77
|
-
invoice.contentHash = await this.calculateContentHash(invoice.xmlContent);
|
|
92
|
+
invoice.contentHash = await this.calculateContentHash(invoice.xmlContent!);
|
|
78
93
|
|
|
79
94
|
// Classify tax scenario
|
|
80
95
|
invoice.taxScenario = this.classifyTaxScenario(invoice);
|
|
@@ -82,7 +97,8 @@ export class InvoiceAdapter {
|
|
|
82
97
|
return invoice;
|
|
83
98
|
} catch (error) {
|
|
84
99
|
this.logger.log('error', `Failed to parse invoice: ${error}`);
|
|
85
|
-
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
101
|
+
throw new Error(`Invoice parsing failed: ${errorMessage}`);
|
|
86
102
|
}
|
|
87
103
|
}
|
|
88
104
|
|
|
@@ -310,7 +326,7 @@ export class InvoiceAdapter {
|
|
|
310
326
|
* Get exemption reason for VAT category
|
|
311
327
|
*/
|
|
312
328
|
private getExemptionReason(categoryCode: string): string | undefined {
|
|
313
|
-
const exemptionReasons: Record<string, string> = {
|
|
329
|
+
const exemptionReasons: Record<string, string | undefined> = {
|
|
314
330
|
'E': 'Tax exempt',
|
|
315
331
|
'Z': 'Zero rated',
|
|
316
332
|
'AE': 'Reverse charge (§13b UStG)',
|
|
@@ -516,7 +532,8 @@ export class InvoiceAdapter {
|
|
|
516
532
|
): Promise<string> {
|
|
517
533
|
try {
|
|
518
534
|
// Load from existing XML
|
|
519
|
-
const
|
|
535
|
+
const EInvoice = await this.getEInvoiceClass();
|
|
536
|
+
const einvoice: any = await EInvoice.fromXml(invoice.xmlContent!);
|
|
520
537
|
|
|
521
538
|
// Convert to target format (takes ~0.6ms)
|
|
522
539
|
const convertedXml = await einvoice.exportXml(targetFormat as any);
|
|
@@ -524,7 +541,8 @@ export class InvoiceAdapter {
|
|
|
524
541
|
return convertedXml;
|
|
525
542
|
} catch (error) {
|
|
526
543
|
this.logger.log('error', `Failed to convert invoice format: ${error}`);
|
|
527
|
-
|
|
544
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
545
|
+
throw new Error(`Format conversion failed: ${errorMessage}`);
|
|
528
546
|
}
|
|
529
547
|
}
|
|
530
548
|
|
|
@@ -537,7 +555,8 @@ export class InvoiceAdapter {
|
|
|
537
555
|
): Promise<{ xml: string; pdf?: Buffer }> {
|
|
538
556
|
try {
|
|
539
557
|
// Create a new invoice instance
|
|
540
|
-
const
|
|
558
|
+
const EInvoice = await this.getEInvoiceClass();
|
|
559
|
+
const einvoice: any = new EInvoice();
|
|
541
560
|
|
|
542
561
|
// Set invoice data
|
|
543
562
|
const businessTerms = this.mapToBusinessTerms(invoiceData);
|
|
@@ -558,7 +577,8 @@ export class InvoiceAdapter {
|
|
|
558
577
|
return { xml, pdf };
|
|
559
578
|
} catch (error) {
|
|
560
579
|
this.logger.log('error', `Failed to generate invoice: ${error}`);
|
|
561
|
-
|
|
580
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
581
|
+
throw new Error(`Invoice generation failed: ${errorMessage}`);
|
|
562
582
|
}
|
|
563
583
|
}
|
|
564
584
|
|
|
@@ -578,4 +598,4 @@ export class InvoiceAdapter {
|
|
|
578
598
|
// This would be a comprehensive mapping in production
|
|
579
599
|
};
|
|
580
600
|
}
|
|
581
|
-
}
|
|
601
|
+
}
|