@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/errors.ts ADDED
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Base error class for all EInvoice-related errors
3
+ */
4
+ export class EInvoiceError extends Error {
5
+ public code: string;
6
+ public details?: any;
7
+ public cause?: Error;
8
+
9
+ constructor(message: string, code: string, details?: any, cause?: Error) {
10
+ super(message);
11
+ this.name = 'EInvoiceError';
12
+ this.code = code;
13
+ this.details = details;
14
+ this.cause = cause;
15
+
16
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
17
+ if (Error.captureStackTrace) {
18
+ Error.captureStackTrace(this, this.constructor);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Returns a detailed error message including cause if available
24
+ */
25
+ public getDetailedMessage(): string {
26
+ let message = `${this.name} [${this.code}]: ${this.message}`;
27
+ if (this.details) {
28
+ message += `\nDetails: ${JSON.stringify(this.details, null, 2)}`;
29
+ }
30
+ if (this.cause) {
31
+ message += `\nCaused by: ${this.cause.message}`;
32
+ }
33
+ return message;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Error thrown when XML parsing fails
39
+ */
40
+ export class EInvoiceParsingError extends EInvoiceError {
41
+ public line?: number;
42
+ public column?: number;
43
+ public xmlSnippet?: string;
44
+
45
+ constructor(
46
+ message: string,
47
+ details?: {
48
+ line?: number;
49
+ column?: number;
50
+ xmlSnippet?: string;
51
+ format?: string;
52
+ },
53
+ cause?: Error
54
+ ) {
55
+ super(message, 'PARSE_ERROR', details, cause);
56
+ this.name = 'EInvoiceParsingError';
57
+ this.line = details?.line;
58
+ this.column = details?.column;
59
+ this.xmlSnippet = details?.xmlSnippet;
60
+ }
61
+
62
+ /**
63
+ * Returns a user-friendly error message with location information
64
+ */
65
+ public getLocationMessage(): string {
66
+ let message = this.message;
67
+ if (this.line !== undefined && this.column !== undefined) {
68
+ message += ` at line ${this.line}, column ${this.column}`;
69
+ }
70
+ if (this.xmlSnippet) {
71
+ message += `\nNear: ${this.xmlSnippet}`;
72
+ }
73
+ return message;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Error thrown when validation fails
79
+ */
80
+ export class EInvoiceValidationError extends EInvoiceError {
81
+ public validationErrors: Array<{
82
+ code: string;
83
+ message: string;
84
+ location?: string;
85
+ severity?: 'error' | 'warning';
86
+ }>;
87
+
88
+ constructor(
89
+ message: string,
90
+ validationErrors: Array<{
91
+ code: string;
92
+ message: string;
93
+ location?: string;
94
+ severity?: 'error' | 'warning';
95
+ }>,
96
+ details?: any
97
+ ) {
98
+ super(message, 'VALIDATION_ERROR', details);
99
+ this.name = 'EInvoiceValidationError';
100
+ this.validationErrors = validationErrors;
101
+ }
102
+
103
+ /**
104
+ * Returns a formatted validation report
105
+ */
106
+ public getValidationReport(): string {
107
+ let report = `${this.message}\n\nValidation errors:\n`;
108
+ for (const error of this.validationErrors) {
109
+ report += `- [${error.code}] ${error.message}`;
110
+ if (error.location) {
111
+ report += ` (at ${error.location})`;
112
+ }
113
+ if (error.severity) {
114
+ report += ` [${error.severity.toUpperCase()}]`;
115
+ }
116
+ report += '\n';
117
+ }
118
+ return report;
119
+ }
120
+
121
+ /**
122
+ * Gets validation errors by severity
123
+ */
124
+ public getErrorsBySeverity(severity: 'error' | 'warning'): typeof this.validationErrors {
125
+ return this.validationErrors.filter(e => (e.severity || 'error') === severity);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Error thrown during PDF operations
131
+ */
132
+ export class EInvoicePDFError extends EInvoiceError {
133
+ public operation: 'extract' | 'embed' | 'create' | 'validate';
134
+ public pdfInfo?: {
135
+ filename?: string;
136
+ size?: number;
137
+ pageCount?: number;
138
+ };
139
+
140
+ constructor(
141
+ message: string,
142
+ operation: 'extract' | 'embed' | 'create' | 'validate',
143
+ details?: any,
144
+ cause?: Error
145
+ ) {
146
+ super(message, `PDF_${operation.toUpperCase()}_ERROR`, details, cause);
147
+ this.name = 'EInvoicePDFError';
148
+ this.operation = operation;
149
+ this.pdfInfo = details?.pdfInfo;
150
+ }
151
+
152
+ /**
153
+ * Returns recovery suggestions based on the operation
154
+ */
155
+ public getRecoverySuggestions(): string[] {
156
+ const suggestions: string[] = [];
157
+
158
+ switch (this.operation) {
159
+ case 'extract':
160
+ suggestions.push(
161
+ 'Ensure the PDF contains embedded XML data',
162
+ 'Check if the PDF is a valid PDF/A-3 document',
163
+ 'Try using a different extraction method',
164
+ 'Verify the PDF is not corrupted'
165
+ );
166
+ break;
167
+ case 'embed':
168
+ suggestions.push(
169
+ 'Ensure the source PDF is valid',
170
+ 'Check that the XML data is well-formed',
171
+ 'Verify sufficient memory is available',
172
+ 'Try with a smaller XML payload'
173
+ );
174
+ break;
175
+ case 'create':
176
+ suggestions.push(
177
+ 'Verify all required invoice data is provided',
178
+ 'Check that the template PDF exists',
179
+ 'Ensure write permissions for output directory'
180
+ );
181
+ break;
182
+ case 'validate':
183
+ suggestions.push(
184
+ 'Check PDF/A-3 compliance',
185
+ 'Verify XML attachment structure',
186
+ 'Ensure proper PDF metadata'
187
+ );
188
+ break;
189
+ }
190
+
191
+ return suggestions;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Error thrown for format-specific issues
197
+ */
198
+ export class EInvoiceFormatError extends EInvoiceError {
199
+ public sourceFormat?: string;
200
+ public targetFormat?: string;
201
+ public unsupportedFeatures?: string[];
202
+
203
+ constructor(
204
+ message: string,
205
+ details: {
206
+ sourceFormat?: string;
207
+ targetFormat?: string;
208
+ unsupportedFeatures?: string[];
209
+ conversionPath?: string;
210
+ },
211
+ cause?: Error
212
+ ) {
213
+ super(message, 'FORMAT_ERROR', details, cause);
214
+ this.name = 'EInvoiceFormatError';
215
+ this.sourceFormat = details.sourceFormat;
216
+ this.targetFormat = details.targetFormat;
217
+ this.unsupportedFeatures = details.unsupportedFeatures;
218
+ }
219
+
220
+ /**
221
+ * Returns a compatibility report
222
+ */
223
+ public getCompatibilityReport(): string {
224
+ let report = this.message;
225
+ if (this.sourceFormat && this.targetFormat) {
226
+ report += `\n\nConversion: ${this.sourceFormat} → ${this.targetFormat}`;
227
+ }
228
+ if (this.unsupportedFeatures && this.unsupportedFeatures.length > 0) {
229
+ report += '\n\nUnsupported features:';
230
+ for (const feature of this.unsupportedFeatures) {
231
+ report += `\n- ${feature}`;
232
+ }
233
+ }
234
+ return report;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Error recovery helper class
240
+ */
241
+ export class ErrorRecovery {
242
+ /**
243
+ * Attempts to recover from a parsing error by cleaning the XML
244
+ */
245
+ public static async attemptXMLRecovery(
246
+ xmlString: string,
247
+ error: EInvoiceParsingError
248
+ ): Promise<{ success: boolean; cleanedXml?: string; message: string }> {
249
+ try {
250
+ let cleanedXml = xmlString;
251
+
252
+ // Remove BOM if present
253
+ if (cleanedXml.charCodeAt(0) === 0xFEFF) {
254
+ cleanedXml = cleanedXml.slice(1);
255
+ }
256
+
257
+ // Fix common encoding issues
258
+ cleanedXml = cleanedXml
259
+ .replace(/&(?!(amp|lt|gt|apos|quot);)/g, '&amp;')
260
+ .replace(/</g, (match, offset) => {
261
+ // Don't replace if it's part of <![CDATA[
262
+ const before = cleanedXml.substring(Math.max(0, offset - 10), offset);
263
+ if (before.includes('CDATA[')) return match;
264
+ return '&lt;';
265
+ });
266
+
267
+ // Try to fix unclosed tags if we have location info
268
+ if (error.line && error.xmlSnippet) {
269
+ // This is a simplified approach - real implementation would be more sophisticated
270
+ const lines = cleanedXml.split('\n');
271
+ if (lines[error.line - 1]) {
272
+ // Attempt basic fixes based on the error
273
+ // This is just a placeholder for more sophisticated recovery
274
+ }
275
+ }
276
+
277
+ return {
278
+ success: true,
279
+ cleanedXml,
280
+ message: 'Applied basic XML cleaning and encoding fixes'
281
+ };
282
+ } catch (recoveryError) {
283
+ return {
284
+ success: false,
285
+ message: `Recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`
286
+ };
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Provides partial data extraction on validation failure
292
+ */
293
+ public static extractPartialData(
294
+ xmlString: string,
295
+ format: string
296
+ ): { success: boolean; partialData?: any; message: string } {
297
+ try {
298
+ // This would implement format-specific partial extraction
299
+ // For now, returning a placeholder
300
+ return {
301
+ success: false,
302
+ message: 'Partial data extraction not yet implemented'
303
+ };
304
+ } catch (error) {
305
+ return {
306
+ success: false,
307
+ message: `Partial extraction failed: ${error instanceof Error ? error.message : String(error)}`
308
+ };
309
+ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Error context builder for detailed error information
315
+ */
316
+ export class ErrorContext {
317
+ private context: Map<string, any> = new Map();
318
+
319
+ public add(key: string, value: any): this {
320
+ this.context.set(key, value);
321
+ return this;
322
+ }
323
+
324
+ public addTimestamp(): this {
325
+ this.context.set('timestamp', new Date().toISOString());
326
+ return this;
327
+ }
328
+
329
+ public addEnvironment(): this {
330
+ this.context.set('environment', {
331
+ nodeVersion: process.version,
332
+ platform: process.platform,
333
+ arch: process.arch
334
+ });
335
+ return this;
336
+ }
337
+
338
+ public build(): Record<string, any> {
339
+ return Object.fromEntries(this.context);
340
+ }
341
+ }
@@ -0,0 +1,68 @@
1
+ import type { TInvoice } from '../../interfaces/common.js';
2
+ import { ValidationLevel } from '../../interfaces/common.js';
3
+ import type { ValidationResult } from '../../interfaces/common.js';
4
+
5
+ /**
6
+ * Base decoder class that defines common decoding functionality
7
+ * for all invoice format decoders
8
+ */
9
+ export abstract class BaseDecoder {
10
+ protected xml: string;
11
+ protected skipValidation: boolean;
12
+
13
+ constructor(xml: string, skipValidation: boolean = false) {
14
+ this.xml = xml;
15
+ this.skipValidation = skipValidation;
16
+ }
17
+
18
+ /**
19
+ * Decodes XML into a TInvoice object
20
+ * @returns Promise resolving to a TInvoice object
21
+ */
22
+ abstract decode(): Promise<TInvoice>;
23
+
24
+ /**
25
+ * Gets letter data in the standard format
26
+ * @returns Promise resolving to a TInvoice object
27
+ */
28
+ public async getLetterData(): Promise<TInvoice> {
29
+ return this.decode();
30
+ }
31
+
32
+ /**
33
+ * Gets the raw XML content
34
+ * @returns XML string
35
+ */
36
+ public getXml(): string {
37
+ return this.xml;
38
+ }
39
+
40
+ /**
41
+ * Parses a CII date string based on format code
42
+ * @param dateStr Date string
43
+ * @param format Format code (e.g., '102' for YYYYMMDD)
44
+ * @returns Timestamp in milliseconds
45
+ */
46
+ protected parseCIIDate(dateStr: string, format?: string): number {
47
+ if (!dateStr) return Date.now();
48
+
49
+ // Format 102 is YYYYMMDD
50
+ if (format === '102' && dateStr.length === 8) {
51
+ const year = parseInt(dateStr.substring(0, 4));
52
+ const month = parseInt(dateStr.substring(4, 6)) - 1; // Month is 0-indexed in JS
53
+ const day = parseInt(dateStr.substring(6, 8));
54
+ return new Date(year, month, day).getTime();
55
+ }
56
+
57
+ // Format 610 is YYYYMM
58
+ if (format === '610' && dateStr.length === 6) {
59
+ const year = parseInt(dateStr.substring(0, 4));
60
+ const month = parseInt(dateStr.substring(4, 6)) - 1;
61
+ return new Date(year, month, 1).getTime();
62
+ }
63
+
64
+ // Try to parse as ISO date or other standard formats
65
+ const parsed = Date.parse(dateStr);
66
+ return isNaN(parsed) ? Date.now() : parsed;
67
+ }
68
+ }
@@ -0,0 +1,14 @@
1
+ import type { TInvoice } from '../../interfaces/common.js';
2
+
3
+ /**
4
+ * Base encoder class that defines common encoding functionality
5
+ * for all invoice format encoders
6
+ */
7
+ export abstract class BaseEncoder {
8
+ /**
9
+ * Encodes a TInvoice object into XML
10
+ * @param invoice TInvoice object to encode
11
+ * @returns XML string
12
+ */
13
+ abstract encode(invoice: TInvoice): Promise<string>;
14
+ }
@@ -0,0 +1,64 @@
1
+ import { ValidationLevel } from '../../interfaces/common.js';
2
+ import type { ValidationResult, ValidationError } from '../../interfaces/common.js';
3
+
4
+ /**
5
+ * Base validator class that defines common validation functionality
6
+ * for all invoice format validators
7
+ */
8
+ export abstract class BaseValidator {
9
+ protected xml: string;
10
+ protected errors: ValidationError[] = [];
11
+
12
+ constructor(xml: string) {
13
+ this.xml = xml;
14
+ }
15
+
16
+ /**
17
+ * Validates XML against the specified level of validation
18
+ * @param level Validation level (syntax, semantic, business)
19
+ * @returns Result of validation
20
+ */
21
+ abstract validate(level?: ValidationLevel): ValidationResult;
22
+
23
+ /**
24
+ * Gets all validation errors found during validation
25
+ * @returns Array of validation errors
26
+ */
27
+ public getValidationErrors(): ValidationError[] {
28
+ return this.errors;
29
+ }
30
+
31
+ /**
32
+ * Checks if the document is valid
33
+ * @returns True if no validation errors were found
34
+ */
35
+ public isValid(): boolean {
36
+ return this.errors.length === 0;
37
+ }
38
+
39
+ /**
40
+ * Validates XML against schema
41
+ * @returns True if schema validation passed
42
+ */
43
+ protected abstract validateSchema(): boolean;
44
+
45
+ /**
46
+ * Validates business rules
47
+ * @returns True if business rule validation passed
48
+ */
49
+ protected abstract validateBusinessRules(): boolean;
50
+
51
+ /**
52
+ * Adds an error to the validation errors list
53
+ * @param code Error code
54
+ * @param message Error message
55
+ * @param location Location in the XML where the error occurred
56
+ */
57
+ protected addError(code: string, message: string, location: string = ''): void {
58
+ this.errors.push({
59
+ code,
60
+ message,
61
+ location
62
+ });
63
+ }
64
+ }
@@ -0,0 +1,139 @@
1
+ import { BaseDecoder } from '../base/base.decoder.js';
2
+ import type { TInvoice, TCreditNote, TDebitNote } from '../../interfaces/common.js';
3
+ import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
4
+ import { DOMParser, xpath } from '../../plugins.js';
5
+
6
+ /**
7
+ * Base decoder for CII-based invoice formats
8
+ */
9
+ export abstract class CIIBaseDecoder extends BaseDecoder {
10
+ protected doc: Document;
11
+ protected namespaces: Record<string, string>;
12
+ protected select: xpath.XPathSelect;
13
+ protected profile: CIIProfile = CIIProfile.EN16931;
14
+
15
+ constructor(xml: string, skipValidation: boolean = false) {
16
+ super(xml, skipValidation);
17
+
18
+ // Parse XML document
19
+ this.doc = new DOMParser().parseFromString(xml, 'application/xml');
20
+
21
+ // Set up namespaces for XPath queries
22
+ this.namespaces = {
23
+ rsm: CII_NAMESPACES.RSM,
24
+ ram: CII_NAMESPACES.RAM,
25
+ udt: CII_NAMESPACES.UDT
26
+ };
27
+
28
+ // Create XPath selector with namespaces
29
+ this.select = xpath.useNamespaces(this.namespaces);
30
+
31
+ // Detect profile
32
+ this.detectProfile();
33
+ }
34
+
35
+ /**
36
+ * Decodes CII XML into a TInvoice object
37
+ * @returns Promise resolving to a TInvoice object
38
+ */
39
+ public async decode(): Promise<TInvoice> {
40
+ // Determine if it's a credit note or debit note based on type code
41
+ const typeCode = this.getText('//ram:TypeCode');
42
+
43
+ if (typeCode === '381') { // Credit note type code
44
+ return this.decodeCreditNote() as unknown as TInvoice;
45
+ } else {
46
+ return this.decodeDebitNote() as unknown as TInvoice;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Detects the CII profile from the XML
52
+ */
53
+ protected detectProfile(): void {
54
+ // Look for profile identifier
55
+ const profileNode = this.select(
56
+ 'string(//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)',
57
+ this.doc
58
+ );
59
+
60
+ if (profileNode) {
61
+ const profileText = profileNode.toString();
62
+
63
+ if (profileText.includes('BASIC')) {
64
+ this.profile = CIIProfile.BASIC;
65
+ } else if (profileText.includes('EN16931')) {
66
+ this.profile = CIIProfile.EN16931;
67
+ } else if (profileText.includes('EXTENDED')) {
68
+ this.profile = CIIProfile.EXTENDED;
69
+ } else if (profileText.includes('MINIMUM')) {
70
+ this.profile = CIIProfile.MINIMUM;
71
+ } else if (profileText.includes('COMFORT')) {
72
+ this.profile = CIIProfile.COMFORT;
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Decodes a CII credit note
79
+ * @returns Promise resolving to a TCreditNote object
80
+ */
81
+ protected abstract decodeCreditNote(): Promise<TCreditNote>;
82
+
83
+ /**
84
+ * Decodes a CII debit note (invoice)
85
+ * @returns Promise resolving to a TDebitNote object
86
+ */
87
+ protected abstract decodeDebitNote(): Promise<TDebitNote>;
88
+
89
+ /**
90
+ * Gets a text value from an XPath expression
91
+ * @param xpath XPath expression
92
+ * @param context Optional context node
93
+ * @returns Text value or empty string if not found
94
+ */
95
+ protected getText(xpathExpr: string, context?: Node): string {
96
+ const node = this.select(xpathExpr, context || this.doc)[0];
97
+ return node ? (node.textContent || '') : '';
98
+ }
99
+
100
+ /**
101
+ * Gets a number value from an XPath expression
102
+ * @param xpath XPath expression
103
+ * @param context Optional context node
104
+ * @returns Number value or 0 if not found or not a number
105
+ */
106
+ protected getNumber(xpathExpr: string, context?: Node): number {
107
+ const text = this.getText(xpathExpr, context);
108
+ const num = parseFloat(text);
109
+ return isNaN(num) ? 0 : num;
110
+ }
111
+
112
+ /**
113
+ * Gets a date value from an XPath expression
114
+ * @param xpath XPath expression
115
+ * @param context Optional context node
116
+ * @returns Date timestamp or current time if not found or invalid
117
+ */
118
+ protected getDate(xpathExpr: string, context?: Node): number {
119
+ const text = this.getText(xpathExpr, context);
120
+ if (!text) return Date.now();
121
+
122
+ const date = new Date(text);
123
+ return isNaN(date.getTime()) ? Date.now() : date.getTime();
124
+ }
125
+
126
+ /**
127
+ * Checks if a node exists
128
+ * @param xpath XPath expression
129
+ * @param context Optional context node
130
+ * @returns True if node exists
131
+ */
132
+ protected exists(xpathExpr: string, context?: Node): boolean {
133
+ const nodes = this.select(xpathExpr, context || this.doc);
134
+ if (Array.isArray(nodes)) {
135
+ return nodes.length > 0;
136
+ }
137
+ return false;
138
+ }
139
+ }
@@ -0,0 +1,64 @@
1
+ import { BaseEncoder } from '../base/base.encoder.js';
2
+ import type { TInvoice, TCreditNote, TDebitNote } from '../../interfaces/common.js';
3
+ import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
4
+
5
+ /**
6
+ * Base encoder for CII-based invoice formats
7
+ */
8
+ export abstract class CIIBaseEncoder extends BaseEncoder {
9
+ protected profile: CIIProfile = CIIProfile.EN16931;
10
+
11
+ /**
12
+ * Sets the CII profile to use for encoding
13
+ * @param profile CII profile
14
+ */
15
+ public setProfile(profile: CIIProfile): void {
16
+ this.profile = profile;
17
+ }
18
+
19
+ /**
20
+ * Encodes a TInvoice object into CII XML
21
+ * @param invoice TInvoice object to encode
22
+ * @returns CII XML string
23
+ */
24
+ public async encode(invoice: TInvoice): Promise<string> {
25
+ // TInvoice is always an invoice, treat it as debit note for encoding
26
+ return this.encodeDebitNote(invoice as unknown as TDebitNote);
27
+ }
28
+
29
+ /**
30
+ * Encodes a TCreditNote object into CII XML
31
+ * @param creditNote TCreditNote object to encode
32
+ * @returns CII XML string
33
+ */
34
+ protected abstract encodeCreditNote(creditNote: TCreditNote): Promise<string>;
35
+
36
+ /**
37
+ * Encodes a TDebitNote object into CII XML
38
+ * @param debitNote TDebitNote object to encode
39
+ * @returns CII XML string
40
+ */
41
+ protected abstract encodeDebitNote(debitNote: TDebitNote): Promise<string>;
42
+
43
+ /**
44
+ * Creates the XML declaration and root element
45
+ * @returns XML string with declaration and root element
46
+ */
47
+ protected createXmlRoot(): string {
48
+ return `<?xml version="1.0" encoding="UTF-8"?>
49
+ <rsm:CrossIndustryInvoice xmlns:rsm="${CII_NAMESPACES.RSM}"
50
+ xmlns:ram="${CII_NAMESPACES.RAM}"
51
+ xmlns:udt="${CII_NAMESPACES.UDT}">
52
+ </rsm:CrossIndustryInvoice>`;
53
+ }
54
+
55
+ /**
56
+ * Formats a date as an ISO string (YYYY-MM-DD)
57
+ * @param timestamp Timestamp to format
58
+ * @returns Formatted date string
59
+ */
60
+ protected formatDate(timestamp: number): string {
61
+ const date = new Date(timestamp);
62
+ return date.toISOString().split('T')[0];
63
+ }
64
+ }