@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.
Files changed (58) hide show
  1. package/{npmextra.json → .smartconfig.json} +12 -6
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/index.d.ts +21 -15
  5. package/dist_ts/index.js +16 -16
  6. package/dist_ts/plugins.d.ts +15 -5
  7. package/dist_ts/plugins.js +42 -6
  8. package/dist_ts/skr.api.js +20 -13
  9. package/dist_ts/skr.classes.account.d.ts +36 -3
  10. package/dist_ts/skr.classes.account.js +366 -210
  11. package/dist_ts/skr.classes.chartofaccounts.d.ts +1 -1
  12. package/dist_ts/skr.classes.chartofaccounts.js +13 -6
  13. package/dist_ts/skr.classes.journalentry.d.ts +9 -4
  14. package/dist_ts/skr.classes.journalentry.js +430 -309
  15. package/dist_ts/skr.classes.ledger.js +7 -1
  16. package/dist_ts/skr.classes.reports.js +4 -1
  17. package/dist_ts/skr.classes.transaction.d.ts +9 -4
  18. package/dist_ts/skr.classes.transaction.js +315 -244
  19. package/dist_ts/skr.export.accounts.js +3 -2
  20. package/dist_ts/skr.export.balances.js +4 -2
  21. package/dist_ts/skr.export.js +6 -3
  22. package/dist_ts/skr.export.ledger.js +4 -3
  23. package/dist_ts/skr.export.pdf.js +6 -3
  24. package/dist_ts/skr.invoice.adapter.d.ts +2 -0
  25. package/dist_ts/skr.invoice.adapter.js +22 -10
  26. package/dist_ts/skr.invoice.booking.js +52 -25
  27. package/dist_ts/skr.invoice.mapper.js +84 -82
  28. package/dist_ts/skr.invoice.storage.js +10 -5
  29. package/dist_ts/skr.postingkeys.d.ts +56 -0
  30. package/dist_ts/skr.postingkeys.js +196 -0
  31. package/dist_ts/skr.security.js +24 -21
  32. package/dist_ts/skr.types.d.ts +18 -0
  33. package/dist_ts/skr03.data.js +3 -1
  34. package/dist_ts/skr04.data.js +3 -1
  35. package/license.md +21 -0
  36. package/package.json +31 -23
  37. package/readme.hints.md +54 -1
  38. package/readme.md +215 -632
  39. package/readme.plan.md +1 -1
  40. package/ts/00_commitinfo_data.ts +8 -0
  41. package/ts/index.ts +43 -15
  42. package/ts/plugins.ts +52 -15
  43. package/ts/skr.api.ts +9 -5
  44. package/ts/skr.classes.account.ts +144 -23
  45. package/ts/skr.classes.chartofaccounts.ts +8 -3
  46. package/ts/skr.classes.journalentry.ts +117 -24
  47. package/ts/skr.classes.ledger.ts +4 -0
  48. package/ts/skr.classes.reports.ts +22 -2
  49. package/ts/skr.classes.transaction.ts +40 -22
  50. package/ts/skr.export.pdf.ts +4 -3
  51. package/ts/skr.invoice.adapter.ts +31 -11
  52. package/ts/skr.invoice.booking.ts +54 -30
  53. package/ts/skr.invoice.storage.ts +3 -2
  54. package/ts/skr.postingkeys.ts +252 -0
  55. package/ts/skr.security.ts +23 -22
  56. package/ts/skr.types.ts +26 -0
  57. package/ts/skr03.data.ts +2 -0
  58. 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
- const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata;
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
- @plugins.smartdata.Collection(() => getDbSync())
14
- export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
36
+ @Collection(() => getDbSync())
37
+ export class JournalEntry extends SmartDataDbDoc {
15
38
  @unI()
16
- public id: string;
39
+ public id!: string;
17
40
 
18
41
  @svDb()
19
42
  @index()
20
- public journalNumber: string;
43
+ public journalNumber!: string;
21
44
 
22
45
  @svDb()
23
46
  @index()
24
- public date: Date;
47
+ public date!: Date;
25
48
 
26
49
  @svDb()
27
50
  @searchable()
28
- public description: string;
51
+ public description!: string;
29
52
 
30
53
  @svDb()
31
54
  @index()
32
- public reference: string;
55
+ public reference!: string;
33
56
 
34
57
  @svDb()
35
- public lines: IJournalEntryLine[];
58
+ public lines!: IJournalEntryLine[];
36
59
 
37
60
  @svDb()
38
61
  @index()
39
- public skrType: TSKRType;
62
+ public skrType!: TSKRType;
40
63
 
41
64
  @svDb()
42
- public totalDebits: number;
65
+ public totalDebits!: number;
43
66
 
44
67
  @svDb()
45
- public totalCredits: number;
68
+ public totalCredits!: number;
46
69
 
47
70
  @svDb()
48
- public isBalanced: boolean;
71
+ public isBalanced!: boolean;
49
72
 
50
73
  @svDb()
51
74
  @index()
52
- public status: 'draft' | 'posted' | 'reversed';
75
+ public status!: 'draft' | 'posted' | 'reversed';
53
76
 
54
77
  @svDb()
55
- public transactionIds: string[];
78
+ public transactionIds!: string[];
56
79
 
57
80
  @svDb()
58
81
  @index()
59
- public period: string;
82
+ public period!: string;
60
83
 
61
84
  @svDb()
62
- public fiscalYear: number;
85
+ public fiscalYear!: number;
63
86
 
64
87
  @svDb()
65
- public createdAt: Date;
88
+ public createdAt!: Date;
66
89
 
67
90
  @svDb()
68
- public postedAt: Date;
91
+ public postedAt!: Date | null;
69
92
 
70
93
  @svDb()
71
- public createdBy: string;
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 are active
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
- throw new Error(
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
- throw new Error(`Account ${line.accountNumber} is not active`);
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({
@@ -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
- const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata;
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
- @plugins.smartdata.Collection(() => getDbSync())
13
- export class Transaction extends SmartDataDbDoc<Transaction, Transaction> {
30
+ @Collection(() => getDbSync())
31
+ export class Transaction extends SmartDataDbDoc {
14
32
  @unI()
15
- public id: string;
33
+ public id!: string;
16
34
 
17
35
  @svDb()
18
36
  @index()
19
- public transactionNumber: string;
37
+ public transactionNumber!: string;
20
38
 
21
39
  @svDb()
22
40
  @index()
23
- public date: Date;
41
+ public date!: Date;
24
42
 
25
43
  @svDb()
26
44
  @index()
27
- public debitAccount: string;
45
+ public debitAccount!: string;
28
46
 
29
47
  @svDb()
30
48
  @index()
31
- public creditAccount: string;
49
+ public creditAccount!: string;
32
50
 
33
51
  @svDb()
34
- public amount: number;
52
+ public amount!: number;
35
53
 
36
54
  @svDb()
37
55
  @searchable()
38
- public description: string;
56
+ public description!: string;
39
57
 
40
58
  @svDb()
41
59
  @index()
42
- public reference: string;
60
+ public reference!: string;
43
61
 
44
62
  @svDb()
45
63
  @index()
46
- public skrType: TSKRType;
64
+ public skrType!: TSKRType;
47
65
 
48
66
  @svDb()
49
- public vatAmount: number;
67
+ public vatAmount!: number;
50
68
 
51
69
  @svDb()
52
- public costCenter: string;
70
+ public costCenter!: string;
53
71
 
54
72
  @svDb()
55
73
  @index()
56
- public status: TTransactionStatus;
74
+ public status!: TTransactionStatus;
57
75
 
58
76
  @svDb()
59
- public reversalOf: string;
77
+ public reversalOf!: string;
60
78
 
61
79
  @svDb()
62
- public reversedBy: string;
80
+ public reversedBy!: string;
63
81
 
64
82
  @svDb()
65
83
  @index()
66
- public period: string; // Format: YYYY-MM
84
+ public period!: string; // Format: YYYY-MM
67
85
 
68
86
  @svDb()
69
- public fiscalYear: number;
87
+ public fiscalYear!: number;
70
88
 
71
89
  @svDb()
72
- public createdAt: Date;
90
+ public createdAt!: Date;
73
91
 
74
92
  @svDb()
75
- public postedAt: Date;
93
+ public postedAt!: Date | null;
76
94
 
77
95
  @svDb()
78
- public createdBy: string;
96
+ public createdBy!: string;
79
97
 
80
98
  constructor(data?: Partial<ITransactionData>) {
81
99
  super();
@@ -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: plugins.smartpdf.SmartPdf | null = null;
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 plugins.smartpdf.SmartPdf();
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
- let einvoice;
61
+ const EInvoice = await this.getEInvoiceClass();
62
+ let einvoice: any;
48
63
  if (typeof file === 'string') {
49
- einvoice = await plugins.einvoice.EInvoice.fromXml(file);
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 plugins.einvoice.EInvoice.fromXml(xmlString);
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
- throw new Error(`Invoice parsing failed: ${error.message}`);
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 einvoice = await plugins.einvoice.EInvoice.fromXml(invoice.xmlContent!);
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
- throw new Error(`Format conversion failed: ${error.message}`);
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 einvoice = new plugins.einvoice.EInvoice();
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
- throw new Error(`Invoice generation failed: ${error.message}`);
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
+ }