@fin.cx/einvoice 5.0.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 (171) hide show
  1. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  2. package/dist_ts/00_commitinfo_data.js +9 -0
  3. package/dist_ts/classes.decoder.d.ts +40 -0
  4. package/dist_ts/classes.decoder.js +320 -0
  5. package/dist_ts/classes.encoder.d.ts +51 -0
  6. package/dist_ts/classes.encoder.js +293 -0
  7. package/dist_ts/classes.xinvoice.d.ts +136 -0
  8. package/dist_ts/classes.xinvoice.js +352 -0
  9. package/dist_ts/einvoice.d.ts +217 -0
  10. package/dist_ts/einvoice.js +505 -0
  11. package/dist_ts/errors.d.ts +121 -0
  12. package/dist_ts/errors.js +241 -0
  13. package/dist_ts/formats/base/base.decoder.d.ts +32 -0
  14. package/dist_ts/formats/base/base.decoder.js +52 -0
  15. package/dist_ts/formats/base/base.encoder.d.ts +13 -0
  16. package/dist_ts/formats/base/base.encoder.js +7 -0
  17. package/dist_ts/formats/base/base.validator.d.ts +44 -0
  18. package/dist_ts/formats/base/base.validator.js +39 -0
  19. package/dist_ts/formats/base.decoder.d.ts +19 -0
  20. package/dist_ts/formats/base.decoder.js +130 -0
  21. package/dist_ts/formats/base.validator.d.ts +44 -0
  22. package/dist_ts/formats/base.validator.js +39 -0
  23. package/dist_ts/formats/cii/cii.decoder.d.ts +61 -0
  24. package/dist_ts/formats/cii/cii.decoder.js +111 -0
  25. package/dist_ts/formats/cii/cii.encoder.d.ts +43 -0
  26. package/dist_ts/formats/cii/cii.encoder.js +48 -0
  27. package/dist_ts/formats/cii/cii.types.d.ts +31 -0
  28. package/dist_ts/formats/cii/cii.types.js +40 -0
  29. package/dist_ts/formats/cii/cii.validator.d.ts +56 -0
  30. package/dist_ts/formats/cii/cii.validator.js +146 -0
  31. package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +43 -0
  32. package/dist_ts/formats/cii/facturx/facturx.decoder.js +207 -0
  33. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +82 -0
  34. package/dist_ts/formats/cii/facturx/facturx.encoder.js +400 -0
  35. package/dist_ts/formats/cii/facturx/facturx.types.d.ts +10 -0
  36. package/dist_ts/formats/cii/facturx/facturx.types.js +15 -0
  37. package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +32 -0
  38. package/dist_ts/formats/cii/facturx/facturx.validator.js +138 -0
  39. package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +43 -0
  40. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +218 -0
  41. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +97 -0
  42. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +550 -0
  43. package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +10 -0
  44. package/dist_ts/formats/cii/zugferd/zugferd.types.js +15 -0
  45. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +49 -0
  46. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +229 -0
  47. package/dist_ts/formats/cii/zugferd/zugferd.validator.d.ts +16 -0
  48. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +29 -0
  49. package/dist_ts/formats/decoder.factory.d.ts +15 -0
  50. package/dist_ts/formats/decoder.factory.js +46 -0
  51. package/dist_ts/formats/factories/decoder.factory.d.ts +13 -0
  52. package/dist_ts/formats/factories/decoder.factory.js +56 -0
  53. package/dist_ts/formats/factories/encoder.factory.d.ts +14 -0
  54. package/dist_ts/formats/factories/encoder.factory.js +40 -0
  55. package/dist_ts/formats/factories/validator.factory.d.ts +12 -0
  56. package/dist_ts/formats/factories/validator.factory.js +110 -0
  57. package/dist_ts/formats/factorx.decoder.d.ts +18 -0
  58. package/dist_ts/formats/factorx.decoder.js +178 -0
  59. package/dist_ts/formats/factorx.encoder.d.ts +51 -0
  60. package/dist_ts/formats/factorx.encoder.js +293 -0
  61. package/dist_ts/formats/facturx.decoder.d.ts +18 -0
  62. package/dist_ts/formats/facturx.decoder.js +210 -0
  63. package/dist_ts/formats/facturx.encoder.d.ts +51 -0
  64. package/dist_ts/formats/facturx.encoder.js +293 -0
  65. package/dist_ts/formats/facturx.validator.d.ts +67 -0
  66. package/dist_ts/formats/facturx.validator.js +266 -0
  67. package/dist_ts/formats/pdf/extractors/associated.extractor.d.ts +14 -0
  68. package/dist_ts/formats/pdf/extractors/associated.extractor.js +68 -0
  69. package/dist_ts/formats/pdf/extractors/base.extractor.d.ts +92 -0
  70. package/dist_ts/formats/pdf/extractors/base.extractor.js +319 -0
  71. package/dist_ts/formats/pdf/extractors/index.d.ts +4 -0
  72. package/dist_ts/formats/pdf/extractors/index.js +5 -0
  73. package/dist_ts/formats/pdf/extractors/standard.extractor.d.ts +13 -0
  74. package/dist_ts/formats/pdf/extractors/standard.extractor.js +74 -0
  75. package/dist_ts/formats/pdf/extractors/text.extractor.d.ts +45 -0
  76. package/dist_ts/formats/pdf/extractors/text.extractor.js +151 -0
  77. package/dist_ts/formats/pdf/pdf.embedder.d.ts +69 -0
  78. package/dist_ts/formats/pdf/pdf.embedder.js +183 -0
  79. package/dist_ts/formats/pdf/pdf.extractor.d.ts +49 -0
  80. package/dist_ts/formats/pdf/pdf.extractor.js +98 -0
  81. package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +40 -0
  82. package/dist_ts/formats/pdf/robust-pdf.extractor.js +324 -0
  83. package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +18 -0
  84. package/dist_ts/formats/ubl/en16931.ubl.validator.js +169 -0
  85. package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +151 -0
  86. package/dist_ts/formats/ubl/generic/ubl.encoder.js +892 -0
  87. package/dist_ts/formats/ubl/ubl.decoder.d.ts +61 -0
  88. package/dist_ts/formats/ubl/ubl.decoder.js +95 -0
  89. package/dist_ts/formats/ubl/ubl.encoder.d.ts +38 -0
  90. package/dist_ts/formats/ubl/ubl.encoder.js +46 -0
  91. package/dist_ts/formats/ubl/ubl.types.d.ts +16 -0
  92. package/dist_ts/formats/ubl/ubl.types.js +21 -0
  93. package/dist_ts/formats/ubl/ubl.validator.d.ts +50 -0
  94. package/dist_ts/formats/ubl/ubl.validator.js +112 -0
  95. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +34 -0
  96. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +424 -0
  97. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +75 -0
  98. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +555 -0
  99. package/dist_ts/formats/ubl/xrechnung.validator.d.ts +15 -0
  100. package/dist_ts/formats/ubl/xrechnung.validator.js +107 -0
  101. package/dist_ts/formats/ubl.validator.d.ts +82 -0
  102. package/dist_ts/formats/ubl.validator.js +306 -0
  103. package/dist_ts/formats/utils/format.detector.d.ts +62 -0
  104. package/dist_ts/formats/utils/format.detector.js +249 -0
  105. package/dist_ts/formats/validation/en16931.validator.d.ts +23 -0
  106. package/dist_ts/formats/validation/en16931.validator.js +119 -0
  107. package/dist_ts/formats/validator.factory.d.ts +18 -0
  108. package/dist_ts/formats/validator.factory.js +78 -0
  109. package/dist_ts/formats/xinvoice.decoder.d.ts +28 -0
  110. package/dist_ts/formats/xinvoice.decoder.js +332 -0
  111. package/dist_ts/formats/xinvoice.encoder.d.ts +19 -0
  112. package/dist_ts/formats/xinvoice.encoder.js +282 -0
  113. package/dist_ts/index.d.ts +51 -0
  114. package/dist_ts/index.js +90 -0
  115. package/dist_ts/interfaces/common.d.ts +79 -0
  116. package/dist_ts/interfaces/common.js +24 -0
  117. package/dist_ts/interfaces.d.ts +87 -0
  118. package/dist_ts/interfaces.js +23 -0
  119. package/dist_ts/plugins.d.ts +14 -0
  120. package/dist_ts/plugins.js +31 -0
  121. package/npmextra.json +35 -0
  122. package/package.json +71 -0
  123. package/readme.hints.md +1107 -0
  124. package/readme.howtofixtests.md +38 -0
  125. package/readme.literature.md +1 -0
  126. package/readme.md +998 -0
  127. package/readme.plan.md +481 -0
  128. package/ts/00_commitinfo_data.ts +8 -0
  129. package/ts/einvoice.ts +603 -0
  130. package/ts/errors.ts +341 -0
  131. package/ts/formats/base/base.decoder.ts +68 -0
  132. package/ts/formats/base/base.encoder.ts +14 -0
  133. package/ts/formats/base/base.validator.ts +64 -0
  134. package/ts/formats/cii/cii.decoder.ts +139 -0
  135. package/ts/formats/cii/cii.encoder.ts +64 -0
  136. package/ts/formats/cii/cii.types.ts +44 -0
  137. package/ts/formats/cii/cii.validator.ts +171 -0
  138. package/ts/formats/cii/facturx/facturx.decoder.ts +243 -0
  139. package/ts/formats/cii/facturx/facturx.encoder.ts +483 -0
  140. package/ts/formats/cii/facturx/facturx.types.ts +18 -0
  141. package/ts/formats/cii/facturx/facturx.validator.ts +180 -0
  142. package/ts/formats/cii/zugferd/zugferd.decoder.ts +256 -0
  143. package/ts/formats/cii/zugferd/zugferd.encoder.ts +661 -0
  144. package/ts/formats/cii/zugferd/zugferd.types.ts +18 -0
  145. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +267 -0
  146. package/ts/formats/cii/zugferd/zugferd.validator.ts +31 -0
  147. package/ts/formats/factories/decoder.factory.ts +62 -0
  148. package/ts/formats/factories/encoder.factory.ts +47 -0
  149. package/ts/formats/factories/validator.factory.ts +134 -0
  150. package/ts/formats/pdf/extractors/associated.extractor.ts +78 -0
  151. package/ts/formats/pdf/extractors/base.extractor.ts +355 -0
  152. package/ts/formats/pdf/extractors/index.ts +4 -0
  153. package/ts/formats/pdf/extractors/standard.extractor.ts +86 -0
  154. package/ts/formats/pdf/extractors/text.extractor.ts +162 -0
  155. package/ts/formats/pdf/pdf.embedder.ts +242 -0
  156. package/ts/formats/pdf/pdf.extractor.ts +141 -0
  157. package/ts/formats/ubl/en16931.ubl.validator.ts +216 -0
  158. package/ts/formats/ubl/generic/ubl.encoder.ts +1041 -0
  159. package/ts/formats/ubl/ubl.decoder.ts +121 -0
  160. package/ts/formats/ubl/ubl.encoder.ts +63 -0
  161. package/ts/formats/ubl/ubl.types.ts +22 -0
  162. package/ts/formats/ubl/ubl.validator.ts +133 -0
  163. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +471 -0
  164. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +619 -0
  165. package/ts/formats/ubl/xrechnung.validator.ts +185 -0
  166. package/ts/formats/utils/format.detector.ts +306 -0
  167. package/ts/formats/validation/en16931.validator.ts +135 -0
  168. package/ts/index.ts +164 -0
  169. package/ts/interfaces/common.ts +90 -0
  170. package/ts/interfaces.ts +98 -0
  171. package/ts/plugins.ts +61 -0
package/ts/einvoice.ts ADDED
@@ -0,0 +1,603 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ import { business, finance } from './plugins.js';
4
+ import type { TInvoice, TAccountingDocItem } from '@tsclass/tsclass/dist_ts/finance/index.js';
5
+ import { InvoiceFormat, ValidationLevel } from './interfaces/common.js';
6
+ import type { ValidationResult, ValidationError, EInvoiceOptions, IPdf, ExportFormat } from './interfaces/common.js';
7
+
8
+ // Import error classes
9
+ import {
10
+ EInvoiceError,
11
+ EInvoiceParsingError,
12
+ EInvoiceValidationError,
13
+ EInvoicePDFError,
14
+ EInvoiceFormatError,
15
+ ErrorContext
16
+ } from './errors.js';
17
+
18
+ // Import factories
19
+ import { DecoderFactory } from './formats/factories/decoder.factory.js';
20
+ import { EncoderFactory } from './formats/factories/encoder.factory.js';
21
+ import { ValidatorFactory } from './formats/factories/validator.factory.js';
22
+
23
+ // Import PDF utilities
24
+ import { PDFEmbedder } from './formats/pdf/pdf.embedder.js';
25
+ import { PDFExtractor } from './formats/pdf/pdf.extractor.js';
26
+
27
+ // Import format detector
28
+ import { FormatDetector } from './formats/utils/format.detector.js';
29
+
30
+ /**
31
+ * Main class for working with electronic invoices.
32
+ * Supports various invoice formats including Factur-X, ZUGFeRD, UBL, and XRechnung
33
+ * Extends the TInvoice interface for seamless integration with existing systems
34
+ */
35
+ export class EInvoice implements TInvoice {
36
+ /**
37
+ * Creates an EInvoice instance from XML string
38
+ * @param xmlString XML string to parse
39
+ * @returns EInvoice instance
40
+ */
41
+ public static async fromXml(xmlString: string): Promise<EInvoice> {
42
+ const invoice = new EInvoice();
43
+ await invoice.fromXmlString(xmlString);
44
+ return invoice;
45
+ }
46
+
47
+ /**
48
+ * Creates an EInvoice instance from file
49
+ * @param filePath Path to the file
50
+ * @returns EInvoice instance
51
+ */
52
+ public static async fromFile(filePath: string): Promise<EInvoice> {
53
+ const invoice = new EInvoice();
54
+ await invoice.fromFile(filePath);
55
+ return invoice;
56
+ }
57
+
58
+ /**
59
+ * Creates an EInvoice instance from PDF
60
+ * @param pdfBuffer PDF buffer
61
+ * @returns EInvoice instance
62
+ */
63
+ public static async fromPdf(pdfBuffer: Buffer | string): Promise<EInvoice> {
64
+ const invoice = new EInvoice();
65
+ if (typeof pdfBuffer === 'string') {
66
+ // If given a file path
67
+ await invoice.fromPdfFile(pdfBuffer);
68
+ } else {
69
+ // If given a buffer, extract XML and parse it
70
+ const extractResult = await invoice.pdfExtractor.extractXml(pdfBuffer);
71
+ if (!extractResult.success || !extractResult.xml) {
72
+ throw new EInvoicePDFError('No invoice XML found in PDF', 'extract');
73
+ }
74
+ await invoice.fromXmlString(extractResult.xml);
75
+ }
76
+ return invoice;
77
+ }
78
+
79
+ // TInvoice interface properties - accounting document structure
80
+ public type: 'accounting-doc' = 'accounting-doc';
81
+ public accountingDocType: 'invoice' = 'invoice';
82
+ public accountingDocId: string = '';
83
+ public accountingDocStatus: 'draft' | 'issued' | 'paid' | 'canceled' | 'refunded' = 'issued';
84
+
85
+ // Business envelope properties
86
+ public id: string = '';
87
+ public date = Date.now();
88
+ public status: 'draft' | 'issued' | 'paid' | 'canceled' | 'refunded' = 'issued';
89
+ public subject: string = '';
90
+ public versionInfo: business.TDocumentEnvelope<string, any>['versionInfo'] = {
91
+ type: 'draft',
92
+ version: '1.0.0'
93
+ };
94
+
95
+ // Contact information
96
+ public from: business.TContact;
97
+ public to: business.TContact;
98
+ public legalContact?: business.TContact;
99
+
100
+ // Additional envelope properties
101
+ public incidenceId: string = '';
102
+ public language: string = 'en';
103
+ public objectActions: any[] = [];
104
+ public pdf: IPdf | null = null;
105
+ public pdfAttachments: IPdf[] | null = null;
106
+ public accentColor: string | null = null;
107
+ public logoUrl: string | null = null;
108
+
109
+ // Accounting document specific properties
110
+ public items: TAccountingDocItem[] = [];
111
+ public dueInDays: number = 30;
112
+ public reverseCharge: boolean = false;
113
+ public currency: finance.TCurrency = 'EUR';
114
+ public notes: string[] = [];
115
+ public periodOfPerformance?: { from: number; to: number };
116
+ public deliveryDate?: number;
117
+ public buyerReference?: string;
118
+ public electronicAddress?: { scheme: string; value: string };
119
+ public paymentOptions?: finance.IPaymentOptionInfo;
120
+ public relatedDocuments?: Array<{
121
+ relationType: 'corrects' | 'replaces' | 'references';
122
+ documentId: string;
123
+ issueDate?: number;
124
+ }>;
125
+ public printResult?: {
126
+ pdfBufferString: string;
127
+ totalNet: number;
128
+ totalGross: number;
129
+ vatGroups: {
130
+ percentage: number;
131
+ items: TAccountingDocItem[];
132
+ }[];
133
+ };
134
+
135
+ // Backward compatibility properties
136
+ public get invoiceId(): string { return this.accountingDocId; }
137
+ public set invoiceId(value: string) { this.accountingDocId = value; }
138
+
139
+ public get invoiceType(): 'invoice' | 'creditnote' | 'debitnote' {
140
+ return this.accountingDocType === 'invoice' ? 'invoice' :
141
+ this.accountingDocType === 'creditnote' ? 'creditnote' : 'debitnote';
142
+ }
143
+ public set invoiceType(value: 'invoice' | 'creditnote' | 'debitnote') {
144
+ this.accountingDocType = 'invoice'; // Always set to invoice for TInvoice type
145
+ }
146
+
147
+ // Computed properties for convenience
148
+ public get issueDate(): Date {
149
+ return new Date(this.date);
150
+ }
151
+ public set issueDate(value: Date) {
152
+ this.date = value.getTime();
153
+ }
154
+
155
+ public get totalNet(): number {
156
+ return this.calculateTotalNet();
157
+ }
158
+
159
+ public get totalVat(): number {
160
+ return this.calculateTotalVat();
161
+ }
162
+
163
+ public get totalGross(): number {
164
+ return this.totalNet + this.totalVat;
165
+ }
166
+
167
+ public get taxBreakdown(): Array<{ taxPercent: number; netAmount: number; taxAmount: number }> {
168
+ return this.calculateTaxBreakdown();
169
+ }
170
+
171
+ // EInvoice specific properties
172
+ public metadata?: {
173
+ format?: InvoiceFormat;
174
+ version?: string;
175
+ profile?: string;
176
+ customizationId?: string;
177
+ extensions?: Record<string, any>;
178
+ };
179
+
180
+ private xmlString: string = '';
181
+ private detectedFormat: InvoiceFormat = InvoiceFormat.UNKNOWN;
182
+ private validationErrors: ValidationError[] = [];
183
+ private options: EInvoiceOptions = {
184
+ validateOnLoad: false,
185
+ validationLevel: ValidationLevel.SYNTAX
186
+ };
187
+
188
+ // PDF utilities
189
+ private pdfEmbedder = new PDFEmbedder();
190
+ private pdfExtractor = new PDFExtractor();
191
+
192
+ /**
193
+ * Creates a new EInvoice instance
194
+ * @param options Configuration options
195
+ */
196
+ constructor(options?: EInvoiceOptions) {
197
+ // Initialize empty contact objects
198
+ this.from = this.createEmptyContact();
199
+ this.to = this.createEmptyContact();
200
+
201
+ // Apply options if provided
202
+ if (options) {
203
+ this.options = { ...this.options, ...options };
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Creates an empty TContact object
209
+ */
210
+ private createEmptyContact(): business.TContact {
211
+ return {
212
+ type: 'company',
213
+ name: '',
214
+ description: '',
215
+ address: {
216
+ streetName: '',
217
+ houseNumber: '',
218
+ city: '',
219
+ postalCode: '',
220
+ country: ''
221
+ },
222
+ registrationDetails: {
223
+ vatId: '',
224
+ registrationId: '',
225
+ registrationName: ''
226
+ },
227
+ status: 'active',
228
+ foundedDate: {
229
+ year: new Date().getFullYear(),
230
+ month: new Date().getMonth() + 1,
231
+ day: new Date().getDate()
232
+ }
233
+ } as business.TCompany;
234
+ }
235
+
236
+ /**
237
+ * Exports the invoice as XML in the specified format
238
+ * @param format The export format
239
+ * @returns XML string
240
+ */
241
+ public async exportXml(format: ExportFormat): Promise<string> {
242
+ return this.toXmlString(format);
243
+ }
244
+
245
+ /**
246
+ * Loads invoice data from XML (alias for fromXmlString)
247
+ * @param xmlString The XML string to parse
248
+ * @returns The EInvoice instance for chaining
249
+ */
250
+ public async loadXml(xmlString: string): Promise<EInvoice> {
251
+ return this.fromXmlString(xmlString);
252
+ }
253
+
254
+ /**
255
+ * Loads invoice data from an XML string
256
+ * @param xmlString The XML string to parse
257
+ * @returns The EInvoice instance for chaining
258
+ */
259
+ public async fromXmlString(xmlString: string): Promise<EInvoice> {
260
+ try {
261
+ this.xmlString = xmlString;
262
+
263
+ // Detect format
264
+ this.detectedFormat = FormatDetector.detectFormat(xmlString);
265
+ if (this.detectedFormat === InvoiceFormat.UNKNOWN) {
266
+ throw new EInvoiceFormatError('Unknown invoice format', { sourceFormat: 'unknown' });
267
+ }
268
+
269
+ // Get appropriate decoder
270
+ const decoder = DecoderFactory.createDecoder(xmlString, !this.options.validateOnLoad);
271
+ const invoice = await decoder.decode();
272
+
273
+ // Map the decoded invoice to our properties
274
+ this.mapFromTInvoice(invoice);
275
+
276
+ // Validate if requested
277
+ if (this.options.validateOnLoad) {
278
+ await this.validate(this.options.validationLevel);
279
+ }
280
+
281
+ return this;
282
+ } catch (error) {
283
+ if (error instanceof EInvoiceError) {
284
+ throw error;
285
+ }
286
+ throw new EInvoiceParsingError(`Failed to parse XML: ${error.message}`, {}, error as Error);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Loads invoice data from a file
292
+ * @param filePath Path to the file to load
293
+ * @returns The EInvoice instance for chaining
294
+ */
295
+ public async fromFile(filePath: string): Promise<EInvoice> {
296
+ try {
297
+ const fileBuffer = await plugins.fs.readFile(filePath);
298
+
299
+ // Check if it's a PDF
300
+ if (filePath.toLowerCase().endsWith('.pdf') || fileBuffer.subarray(0, 4).toString() === '%PDF') {
301
+ return this.fromPdfFile(filePath);
302
+ }
303
+
304
+ // Otherwise treat as XML
305
+ const xmlString = fileBuffer.toString('utf-8');
306
+ return this.fromXmlString(xmlString);
307
+ } catch (error) {
308
+ throw new EInvoiceError(`Failed to load file: ${error.message}`, 'FILE_LOAD_ERROR', { filePath });
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Loads invoice data from a PDF file
314
+ * @param filePath Path to the PDF file
315
+ * @returns The EInvoice instance for chaining
316
+ */
317
+ public async fromPdfFile(filePath: string): Promise<EInvoice> {
318
+ try {
319
+ const pdfBuffer = await plugins.fs.readFile(filePath);
320
+ const extractResult = await this.pdfExtractor.extractXml(pdfBuffer);
321
+ const extractedXml = extractResult.success ? extractResult.xml : null;
322
+
323
+ if (!extractedXml) {
324
+ throw new EInvoicePDFError('No invoice XML found in PDF', 'extract', { filePath });
325
+ }
326
+
327
+ // Store the PDF for later use
328
+ this.pdf = {
329
+ name: plugins.path.basename(filePath),
330
+ id: plugins.crypto.createHash('md5').update(pdfBuffer).digest('hex'),
331
+ buffer: new Uint8Array(pdfBuffer),
332
+ metadata: {
333
+ textExtraction: '',
334
+ format: 'PDF/A-3',
335
+ embeddedXml: {
336
+ filename: 'factur-x.xml',
337
+ description: 'Factur-X Invoice'
338
+ }
339
+ }
340
+ };
341
+
342
+ return this.fromXmlString(extractedXml);
343
+ } catch (error) {
344
+ if (error instanceof EInvoiceError) {
345
+ throw error;
346
+ }
347
+ throw new EInvoicePDFError(`Failed to extract invoice from PDF: ${error.message}`, 'extract', {}, error as Error);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Maps data from a TInvoice to this EInvoice instance
353
+ */
354
+ private mapFromTInvoice(invoice: TInvoice): void {
355
+ // Map all properties from the decoded invoice
356
+ Object.assign(this, invoice);
357
+
358
+ // Ensure backward compatibility
359
+ if (!this.id && this.accountingDocId) {
360
+ this.id = this.accountingDocId;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Maps this EInvoice instance to a TInvoice
366
+ */
367
+ private mapToTInvoice(): TInvoice {
368
+ const invoice: any = {
369
+ type: 'accounting-doc',
370
+ accountingDocType: this.accountingDocType,
371
+ accountingDocId: this.accountingDocId || this.id,
372
+ accountingDocStatus: this.accountingDocStatus,
373
+ id: this.id,
374
+ date: this.date,
375
+ status: this.status,
376
+ subject: this.subject,
377
+ versionInfo: this.versionInfo,
378
+ from: this.from,
379
+ to: this.to,
380
+ legalContact: this.legalContact,
381
+ incidenceId: this.incidenceId,
382
+ language: this.language,
383
+ objectActions: this.objectActions,
384
+ items: this.items,
385
+ dueInDays: this.dueInDays,
386
+ reverseCharge: this.reverseCharge,
387
+ currency: this.currency,
388
+ notes: this.notes,
389
+ periodOfPerformance: this.periodOfPerformance,
390
+ deliveryDate: this.deliveryDate,
391
+ buyerReference: this.buyerReference,
392
+ electronicAddress: this.electronicAddress,
393
+ paymentOptions: this.paymentOptions,
394
+ relatedDocuments: this.relatedDocuments,
395
+ printResult: this.printResult
396
+ };
397
+
398
+ // Preserve metadata for enhanced spec compliance
399
+ if ((this as any).metadata) {
400
+ invoice.metadata = (this as any).metadata;
401
+ }
402
+
403
+ return invoice;
404
+ }
405
+
406
+ /**
407
+ * Exports the invoice to an XML string in the specified format
408
+ * @param format The target format
409
+ * @returns The XML string
410
+ */
411
+ public async toXmlString(format: ExportFormat): Promise<string> {
412
+ try {
413
+ const encoder = EncoderFactory.createEncoder(format);
414
+ const invoice = this.mapToTInvoice();
415
+
416
+ // Import EN16931Validator dynamically to avoid circular dependency
417
+ const { EN16931Validator } = await import('./formats/validation/en16931.validator.js');
418
+
419
+ // Validate mandatory fields before encoding
420
+ EN16931Validator.validateMandatoryFields(invoice);
421
+
422
+ return await encoder.encode(invoice);
423
+ } catch (error) {
424
+ throw new EInvoiceFormatError(`Failed to encode to ${format}: ${error.message}`, { targetFormat: format });
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Validates the invoice
430
+ * @param level The validation level to use
431
+ * @returns The validation result
432
+ */
433
+ public async validate(level: ValidationLevel = ValidationLevel.BUSINESS): Promise<ValidationResult> {
434
+ try {
435
+ const format = this.detectedFormat || InvoiceFormat.UNKNOWN;
436
+ if (format === InvoiceFormat.UNKNOWN) {
437
+ throw new EInvoiceValidationError('Cannot validate: format unknown', []);
438
+ }
439
+
440
+ const validator = ValidatorFactory.createValidator(this.xmlString);
441
+ const result = validator.validate(level);
442
+
443
+ this.validationErrors = result.errors;
444
+ return result;
445
+ } catch (error) {
446
+ if (error instanceof EInvoiceError) {
447
+ throw error;
448
+ }
449
+ throw new EInvoiceValidationError(`Validation failed: ${error.message}`, [], { validationLevel: level });
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Embeds the invoice XML into a PDF
455
+ * @param pdfBuffer The PDF buffer to embed into
456
+ * @param format The format to use for embedding
457
+ * @returns The PDF buffer with embedded XML
458
+ */
459
+ public async embedInPdf(pdfBuffer: Buffer, format: ExportFormat = 'facturx'): Promise<Buffer> {
460
+ try {
461
+ const xmlString = await this.toXmlString(format);
462
+ const embedResult = await this.pdfEmbedder.embedXml(pdfBuffer, xmlString, 'invoice.xml', `${format} Invoice`);
463
+ if (!embedResult.success) {
464
+ throw new EInvoicePDFError('Failed to embed XML in PDF', 'embed', { format });
465
+ }
466
+ return embedResult.data! as Buffer;
467
+ } catch (error) {
468
+ throw new EInvoicePDFError(`Failed to embed XML in PDF: ${error.message}`, 'embed', { format }, error as Error);
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Saves the invoice to a file
474
+ * @param filePath The path to save to
475
+ * @param format The format to save in
476
+ */
477
+ public async saveToFile(filePath: string, format?: ExportFormat): Promise<void> {
478
+ try {
479
+ // Determine format from file extension if not provided
480
+ if (!format && filePath.toLowerCase().endsWith('.xml')) {
481
+ format = this.detectedFormat === InvoiceFormat.UBL ? 'ubl' :
482
+ this.detectedFormat === InvoiceFormat.ZUGFERD ? 'zugferd' :
483
+ this.detectedFormat === InvoiceFormat.FACTURX ? 'facturx' :
484
+ 'xrechnung';
485
+ }
486
+
487
+ if (filePath.toLowerCase().endsWith('.pdf')) {
488
+ // Save as PDF with embedded XML
489
+ if (!this.pdf) {
490
+ throw new EInvoiceError('No PDF available to save', 'NO_PDF_ERROR');
491
+ }
492
+ const pdfWithXml = await this.embedInPdf(Buffer.from(this.pdf.buffer), format);
493
+ await plugins.fs.writeFile(filePath, pdfWithXml);
494
+ } else {
495
+ // Save as XML
496
+ const xmlString = await this.toXmlString(format || 'xrechnung');
497
+ await plugins.fs.writeFile(filePath, xmlString, 'utf-8');
498
+ }
499
+ } catch (error) {
500
+ if (error instanceof EInvoiceError) {
501
+ throw error;
502
+ }
503
+ throw new EInvoiceError(`Failed to save file: ${error.message}`, 'FILE_SAVE_ERROR', { filePath });
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Gets the validation errors
509
+ * @returns Array of validation errors
510
+ */
511
+ public getValidationErrors(): ValidationError[] {
512
+ return this.validationErrors;
513
+ }
514
+
515
+ /**
516
+ * Checks if the invoice is valid
517
+ * @returns True if valid, false otherwise
518
+ */
519
+ public isValid(): boolean {
520
+ return this.validationErrors.length === 0;
521
+ }
522
+
523
+ /**
524
+ * Gets the detected format
525
+ * @returns The detected invoice format
526
+ */
527
+ public getFormat(): InvoiceFormat {
528
+ return this.detectedFormat;
529
+ }
530
+
531
+ /**
532
+ * Gets the original XML string
533
+ * @returns The XML string
534
+ */
535
+ public getXml(): string {
536
+ return this.xmlString;
537
+ }
538
+
539
+ /**
540
+ * Calculates the total net amount
541
+ */
542
+ private calculateTotalNet(): number {
543
+ return this.items.reduce((sum, item) => {
544
+ return sum + (item.unitQuantity * item.unitNetPrice);
545
+ }, 0);
546
+ }
547
+
548
+ /**
549
+ * Calculates the total VAT amount
550
+ */
551
+ private calculateTotalVat(): number {
552
+ return this.items.reduce((sum, item) => {
553
+ const net = item.unitQuantity * item.unitNetPrice;
554
+ return sum + (net * item.vatPercentage / 100);
555
+ }, 0);
556
+ }
557
+
558
+ /**
559
+ * Calculates tax breakdown by rate
560
+ */
561
+ private calculateTaxBreakdown(): Array<{ taxPercent: number; netAmount: number; taxAmount: number }> {
562
+ const breakdown = new Map<number, { net: number; tax: number }>();
563
+
564
+ this.items.forEach(item => {
565
+ const net = item.unitQuantity * item.unitNetPrice;
566
+ const tax = net * item.vatPercentage / 100;
567
+
568
+ const current = breakdown.get(item.vatPercentage) || { net: 0, tax: 0 };
569
+ breakdown.set(item.vatPercentage, {
570
+ net: current.net + net,
571
+ tax: current.tax + tax
572
+ });
573
+ });
574
+
575
+ return Array.from(breakdown.entries()).map(([rate, amounts]) => ({
576
+ taxPercent: rate,
577
+ netAmount: amounts.net,
578
+ taxAmount: amounts.tax
579
+ }));
580
+ }
581
+
582
+ /**
583
+ * Creates a new invoice item
584
+ */
585
+ public createItem(data: Partial<TAccountingDocItem>): TAccountingDocItem {
586
+ return {
587
+ position: data.position || this.items.length + 1,
588
+ name: data.name || '',
589
+ articleNumber: data.articleNumber,
590
+ unitType: data.unitType || 'unit',
591
+ unitQuantity: data.unitQuantity || 1,
592
+ unitNetPrice: data.unitNetPrice || 0,
593
+ vatPercentage: data.vatPercentage || 0
594
+ };
595
+ }
596
+
597
+ /**
598
+ * Adds an item to the invoice
599
+ */
600
+ public addItem(item: Partial<TAccountingDocItem>): void {
601
+ this.items.push(this.createItem(item));
602
+ }
603
+ }