@fin.cx/einvoice 5.0.2 → 5.1.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 (116) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/einvoice.d.ts +4 -8
  3. package/dist_ts/einvoice.js +46 -7
  4. package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +21 -0
  5. package/dist_ts/formats/converters/xml-to-einvoice.converter.js +127 -0
  6. package/dist_ts/formats/semantic/bt-bg.model.d.ts +445 -0
  7. package/dist_ts/formats/semantic/bt-bg.model.js +12 -0
  8. package/dist_ts/formats/semantic/semantic.adapter.d.ts +87 -0
  9. package/dist_ts/formats/semantic/semantic.adapter.js +538 -0
  10. package/dist_ts/formats/semantic/semantic.validator.d.ts +48 -0
  11. package/dist_ts/formats/semantic/semantic.validator.js +596 -0
  12. package/dist_ts/formats/utils/currency.calculator.decimal.d.ts +98 -0
  13. package/dist_ts/formats/utils/currency.calculator.decimal.js +212 -0
  14. package/dist_ts/formats/utils/currency.utils.d.ts +99 -0
  15. package/dist_ts/formats/utils/currency.utils.js +250 -0
  16. package/dist_ts/formats/utils/decimal.d.ts +151 -0
  17. package/dist_ts/formats/utils/decimal.js +435 -0
  18. package/dist_ts/formats/validation/codelist.validator.d.ts +49 -0
  19. package/dist_ts/formats/validation/codelist.validator.js +188 -0
  20. package/dist_ts/formats/validation/conformance.harness.d.ts +99 -0
  21. package/dist_ts/formats/validation/conformance.harness.js +471 -0
  22. package/dist_ts/formats/validation/en16931.business-rules.validator.d.ts +58 -0
  23. package/dist_ts/formats/validation/en16931.business-rules.validator.js +459 -0
  24. package/dist_ts/formats/validation/facturx.validator.d.ts +75 -0
  25. package/dist_ts/formats/validation/facturx.validator.js +522 -0
  26. package/dist_ts/formats/validation/integrated.validator.d.ts +82 -0
  27. package/dist_ts/formats/validation/integrated.validator.js +311 -0
  28. package/dist_ts/formats/validation/peppol.validator.d.ts +77 -0
  29. package/dist_ts/formats/validation/peppol.validator.js +529 -0
  30. package/dist_ts/formats/validation/schematron.downloader.d.ts +71 -0
  31. package/dist_ts/formats/validation/schematron.downloader.js +254 -0
  32. package/dist_ts/formats/validation/schematron.integration.d.ts +60 -0
  33. package/dist_ts/formats/validation/schematron.integration.js +230 -0
  34. package/dist_ts/formats/validation/schematron.validator.d.ts +79 -0
  35. package/dist_ts/formats/validation/schematron.validator.js +288 -0
  36. package/dist_ts/formats/validation/schematron.worker.d.ts +50 -0
  37. package/dist_ts/formats/validation/schematron.worker.js +185 -0
  38. package/dist_ts/formats/validation/validation.types.d.ts +163 -0
  39. package/dist_ts/formats/validation/validation.types.js +181 -0
  40. package/dist_ts/formats/validation/vat-categories.validator.d.ts +87 -0
  41. package/dist_ts/formats/validation/vat-categories.validator.js +497 -0
  42. package/dist_ts/formats/validation/xrechnung.validator.d.ts +92 -0
  43. package/dist_ts/formats/validation/xrechnung.validator.js +417 -0
  44. package/dist_ts/interfaces/common.d.ts +1 -0
  45. package/dist_ts/interfaces/en16931-metadata.d.ts +83 -0
  46. package/dist_ts/interfaces/en16931-metadata.js +6 -0
  47. package/dist_ts/plugins.d.ts +3 -1
  48. package/dist_ts/plugins.js +7 -2
  49. package/dist_ts_install/download-schematron.d.ts +6 -0
  50. package/dist_ts_install/download-schematron.js +62 -0
  51. package/dist_ts_install/download-test-samples.d.ts +19 -0
  52. package/dist_ts_install/download-test-samples.js +169 -0
  53. package/dist_ts_install/download-xrechnung-rules.d.ts +7 -0
  54. package/dist_ts_install/download-xrechnung-rules.js +146 -0
  55. package/dist_ts_install/index.d.ts +8 -0
  56. package/dist_ts_install/index.js +246 -0
  57. package/package.json +13 -8
  58. package/readme.md +255 -810
  59. package/ts/00_commitinfo_data.ts +1 -1
  60. package/ts/einvoice.ts +63 -14
  61. package/ts/formats/converters/xml-to-einvoice.converter.ts +142 -0
  62. package/ts/formats/semantic/bt-bg.model.ts +524 -0
  63. package/ts/formats/semantic/semantic.adapter.ts +600 -0
  64. package/ts/formats/semantic/semantic.validator.ts +654 -0
  65. package/ts/formats/utils/currency.calculator.decimal.ts +323 -0
  66. package/ts/formats/utils/currency.utils.ts +299 -0
  67. package/ts/formats/utils/decimal.ts +509 -0
  68. package/ts/formats/validation/codelist.validator.ts +317 -0
  69. package/ts/formats/validation/conformance.harness.ts +591 -0
  70. package/ts/formats/validation/en16931.business-rules.validator.ts +694 -0
  71. package/ts/formats/validation/facturx.validator.ts +579 -0
  72. package/ts/formats/validation/integrated.validator.ts +405 -0
  73. package/ts/formats/validation/peppol.validator.ts +589 -0
  74. package/ts/formats/validation/schematron.downloader.ts +304 -0
  75. package/ts/formats/validation/schematron.integration.ts +285 -0
  76. package/ts/formats/validation/schematron.validator.ts +348 -0
  77. package/ts/formats/validation/schematron.worker.ts +221 -0
  78. package/ts/formats/validation/validation.types.ts +274 -0
  79. package/ts/formats/validation/vat-categories.validator.ts +845 -0
  80. package/ts/formats/validation/xrechnung.validator.ts +494 -0
  81. package/ts/interfaces/common.ts +1 -0
  82. package/ts/interfaces/en16931-metadata.ts +104 -0
  83. package/ts/plugins.ts +8 -0
  84. package/ts/tspublish.json +3 -0
  85. package/dist_ts/classes.decoder.d.ts +0 -40
  86. package/dist_ts/classes.decoder.js +0 -320
  87. package/dist_ts/classes.encoder.d.ts +0 -51
  88. package/dist_ts/classes.encoder.js +0 -293
  89. package/dist_ts/classes.xinvoice.d.ts +0 -136
  90. package/dist_ts/classes.xinvoice.js +0 -352
  91. package/dist_ts/formats/base.decoder.d.ts +0 -19
  92. package/dist_ts/formats/base.decoder.js +0 -130
  93. package/dist_ts/formats/base.validator.d.ts +0 -44
  94. package/dist_ts/formats/base.validator.js +0 -39
  95. package/dist_ts/formats/decoder.factory.d.ts +0 -15
  96. package/dist_ts/formats/decoder.factory.js +0 -46
  97. package/dist_ts/formats/factorx.decoder.d.ts +0 -18
  98. package/dist_ts/formats/factorx.decoder.js +0 -178
  99. package/dist_ts/formats/factorx.encoder.d.ts +0 -51
  100. package/dist_ts/formats/factorx.encoder.js +0 -293
  101. package/dist_ts/formats/facturx.decoder.d.ts +0 -18
  102. package/dist_ts/formats/facturx.decoder.js +0 -210
  103. package/dist_ts/formats/facturx.encoder.d.ts +0 -51
  104. package/dist_ts/formats/facturx.encoder.js +0 -293
  105. package/dist_ts/formats/facturx.validator.d.ts +0 -67
  106. package/dist_ts/formats/facturx.validator.js +0 -266
  107. package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +0 -40
  108. package/dist_ts/formats/pdf/robust-pdf.extractor.js +0 -324
  109. package/dist_ts/formats/ubl.validator.d.ts +0 -82
  110. package/dist_ts/formats/ubl.validator.js +0 -306
  111. package/dist_ts/formats/validator.factory.d.ts +0 -18
  112. package/dist_ts/formats/validator.factory.js +0 -78
  113. package/dist_ts/formats/xinvoice.decoder.d.ts +0 -28
  114. package/dist_ts/formats/xinvoice.decoder.js +0 -332
  115. package/dist_ts/formats/xinvoice.encoder.d.ts +0 -19
  116. package/dist_ts/formats/xinvoice.encoder.js +0 -282
@@ -0,0 +1,494 @@
1
+ /**
2
+ * XRechnung CIUS Validator
3
+ * Implements German-specific validation rules for XRechnung 3.0
4
+ *
5
+ * XRechnung is the German Core Invoice Usage Specification (CIUS) of EN16931
6
+ * Required for B2G invoicing in Germany since November 2020
7
+ */
8
+
9
+ import type { EInvoice } from '../../einvoice.js';
10
+ import type { ValidationResult } from './validation.types.js';
11
+
12
+ /**
13
+ * XRechnung-specific validator implementing German CIUS rules
14
+ */
15
+ export class XRechnungValidator {
16
+ private static readonly LEITWEG_ID_PATTERN = /^[0-9]{2,3}-[0-9]{1,12}-[0-9]{2,30}$/;
17
+ private static readonly IBAN_PATTERNS: Record<string, { length: number; pattern: RegExp }> = {
18
+ DE: { length: 22, pattern: /^DE[0-9]{2}[0-9]{8}[0-9]{10}$/ },
19
+ AT: { length: 20, pattern: /^AT[0-9]{2}[0-9]{5}[0-9]{11}$/ },
20
+ CH: { length: 21, pattern: /^CH[0-9]{2}[0-9]{5}[0-9A-Z]{12}$/ },
21
+ FR: { length: 27, pattern: /^FR[0-9]{2}[0-9]{5}[0-9]{5}[0-9A-Z]{11}[0-9]{2}$/ },
22
+ NL: { length: 18, pattern: /^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/ },
23
+ BE: { length: 16, pattern: /^BE[0-9]{2}[0-9]{3}[0-9]{7}[0-9]{2}$/ },
24
+ IT: { length: 27, pattern: /^IT[0-9]{2}[A-Z][0-9]{5}[0-9]{5}[0-9A-Z]{12}$/ },
25
+ ES: { length: 24, pattern: /^ES[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{2}[0-9]{10}$/ }
26
+ };
27
+ private static readonly BIC_PATTERN = /^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/;
28
+
29
+ // SEPA countries
30
+ private static readonly SEPA_COUNTRIES = new Set([
31
+ 'AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI',
32
+ 'FR', 'GB', 'GI', 'GR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU',
33
+ 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'SM', 'VA'
34
+ ]);
35
+
36
+ /**
37
+ * Validate XRechnung-specific requirements
38
+ */
39
+ validateXRechnung(invoice: EInvoice): ValidationResult[] {
40
+ const results: ValidationResult[] = [];
41
+
42
+ // Check if this is an XRechnung invoice
43
+ if (!this.isXRechnungInvoice(invoice)) {
44
+ return results; // Not XRechnung, skip validation
45
+ }
46
+
47
+ // Validate mandatory fields
48
+ results.push(...this.validateLeitwegId(invoice));
49
+ results.push(...this.validateBuyerReference(invoice));
50
+ results.push(...this.validatePaymentDetails(invoice));
51
+ results.push(...this.validateSellerContact(invoice));
52
+ results.push(...this.validateTaxRegistration(invoice));
53
+
54
+ return results;
55
+ }
56
+
57
+ /**
58
+ * Check if invoice is XRechnung based on profile/customization ID
59
+ */
60
+ private isXRechnungInvoice(invoice: EInvoice): boolean {
61
+ const profileId = invoice.metadata?.profileId || '';
62
+ const customizationId = invoice.metadata?.customizationId || '';
63
+
64
+ // XRechnung profile identifiers
65
+ const xrechnungProfiles = [
66
+ 'urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0',
67
+ 'urn:cen.eu:en16931:2017#conformant#urn:xeinkauf.de:kosit:xrechnung_3.0',
68
+ 'urn:cen.eu:en16931:2017:xrechnung',
69
+ 'xrechnung'
70
+ ];
71
+
72
+ return xrechnungProfiles.some(profile =>
73
+ profileId.toLowerCase().includes(profile.toLowerCase()) ||
74
+ customizationId.toLowerCase().includes(profile.toLowerCase())
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Validate Leitweg-ID (routing ID for German public administration)
80
+ * Pattern: [0-9]{2,3}-[0-9]{1,12}-[0-9]{2,30}
81
+ * Rule: XR-DE-01
82
+ */
83
+ private validateLeitwegId(invoice: EInvoice): ValidationResult[] {
84
+ const results: ValidationResult[] = [];
85
+
86
+ // Leitweg-ID is typically in buyer reference (BT-10) for B2G
87
+ const buyerReference = invoice.metadata?.buyerReference || invoice.buyerReference || '';
88
+
89
+ // Check if it looks like a Leitweg-ID
90
+ if (buyerReference && this.looksLikeLeitwegId(buyerReference)) {
91
+ if (!XRechnungValidator.LEITWEG_ID_PATTERN.test(buyerReference.trim())) {
92
+ results.push({
93
+ ruleId: 'XR-DE-01',
94
+ severity: 'error',
95
+ source: 'XRECHNUNG',
96
+ message: `Invalid Leitweg-ID format: ${buyerReference}. Expected pattern: [0-9]{2,3}-[0-9]{1,12}-[0-9]{2,30}`,
97
+ btReference: 'BT-10',
98
+ field: 'buyerReference',
99
+ value: buyerReference
100
+ });
101
+ }
102
+ }
103
+
104
+ // For B2G invoices, Leitweg-ID might be mandatory
105
+ if (this.isB2GInvoice(invoice) && !buyerReference) {
106
+ results.push({
107
+ ruleId: 'XR-DE-15',
108
+ severity: 'error',
109
+ source: 'XRECHNUNG',
110
+ message: 'Buyer reference (Leitweg-ID) is mandatory for B2G invoices in Germany',
111
+ btReference: 'BT-10',
112
+ field: 'buyerReference'
113
+ });
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ /**
120
+ * Check if string looks like a Leitweg-ID
121
+ */
122
+ private looksLikeLeitwegId(value: string): boolean {
123
+ // Contains dashes and numbers in the right proportion
124
+ return value.includes('-') && /^\d+-\d+-\d+$/.test(value.trim());
125
+ }
126
+
127
+ /**
128
+ * Check if this is a B2G invoice
129
+ */
130
+ private isB2GInvoice(invoice: EInvoice): boolean {
131
+ // Check if buyer is a public entity (simplified check)
132
+ const buyerName = invoice.to?.name?.toLowerCase() || '';
133
+ const buyerType = invoice.metadata?.extensions?.buyerType?.toLowerCase() || '';
134
+
135
+ const publicIndicators = [
136
+ 'bundesamt', 'landesamt', 'stadtverwaltung', 'gemeinde',
137
+ 'ministerium', 'behörde', 'öffentlich', 'public', 'government'
138
+ ];
139
+
140
+ return publicIndicators.some(indicator =>
141
+ buyerName.includes(indicator) || buyerType.includes(indicator)
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Validate mandatory buyer reference (BT-10)
147
+ * Rule: XR-DE-15
148
+ */
149
+ private validateBuyerReference(invoice: EInvoice): ValidationResult[] {
150
+ const results: ValidationResult[] = [];
151
+
152
+ const buyerReference = invoice.metadata?.buyerReference || invoice.buyerReference || '';
153
+
154
+ // Skip if B2G invoice - already handled in validateLeitwegId
155
+ if (this.isB2GInvoice(invoice)) {
156
+ return results;
157
+ }
158
+
159
+ if (!buyerReference || buyerReference.trim().length === 0) {
160
+ results.push({
161
+ ruleId: 'XR-DE-15',
162
+ severity: 'error',
163
+ source: 'XRECHNUNG',
164
+ message: 'Buyer reference (BT-10) is mandatory in XRechnung',
165
+ btReference: 'BT-10',
166
+ field: 'buyerReference'
167
+ });
168
+ }
169
+
170
+ return results;
171
+ }
172
+
173
+ /**
174
+ * Validate payment details (IBAN/BIC for SEPA)
175
+ * Rules: XR-DE-19, XR-DE-20
176
+ */
177
+ private validatePaymentDetails(invoice: EInvoice): ValidationResult[] {
178
+ const results: ValidationResult[] = [];
179
+
180
+ // Check payment means
181
+ const paymentMeans = invoice.metadata?.extensions?.paymentMeans as Array<{
182
+ type?: string;
183
+ iban?: string;
184
+ bic?: string;
185
+ accountName?: string;
186
+ }> | undefined;
187
+ if (!paymentMeans || paymentMeans.length === 0) {
188
+ return results; // No payment details to validate
189
+ }
190
+
191
+ for (const payment of paymentMeans) {
192
+ // Validate IBAN if present
193
+ if (payment.iban) {
194
+ const ibanResult = this.validateIBAN(payment.iban);
195
+ if (!ibanResult.valid) {
196
+ results.push({
197
+ ruleId: 'XR-DE-19',
198
+ severity: 'error',
199
+ source: 'XRECHNUNG',
200
+ message: `Invalid IBAN: ${ibanResult.message}`,
201
+ btReference: 'BT-84',
202
+ field: 'iban',
203
+ value: payment.iban
204
+ });
205
+ }
206
+
207
+ // Check if IBAN country is in SEPA zone
208
+ const countryCode = payment.iban.substring(0, 2);
209
+ if (!XRechnungValidator.SEPA_COUNTRIES.has(countryCode)) {
210
+ results.push({
211
+ ruleId: 'XR-DE-19',
212
+ severity: 'warning',
213
+ source: 'XRECHNUNG',
214
+ message: `IBAN country ${countryCode} is not in SEPA zone`,
215
+ btReference: 'BT-84',
216
+ field: 'iban',
217
+ value: payment.iban
218
+ });
219
+ }
220
+ }
221
+
222
+ // Validate BIC if present
223
+ if (payment.bic) {
224
+ const bicResult = this.validateBIC(payment.bic);
225
+ if (!bicResult.valid) {
226
+ results.push({
227
+ ruleId: 'XR-DE-20',
228
+ severity: 'error',
229
+ source: 'XRECHNUNG',
230
+ message: `Invalid BIC: ${bicResult.message}`,
231
+ btReference: 'BT-86',
232
+ field: 'bic',
233
+ value: payment.bic
234
+ });
235
+ }
236
+ }
237
+
238
+ // For German domestic payments, BIC is optional if IBAN starts with DE
239
+ if (payment.iban?.startsWith('DE') && !payment.bic) {
240
+ // This is fine, BIC is optional for domestic German payments
241
+ } else if (payment.iban && !payment.iban.startsWith('DE') && !payment.bic) {
242
+ results.push({
243
+ ruleId: 'XR-DE-20',
244
+ severity: 'warning',
245
+ source: 'XRECHNUNG',
246
+ message: 'BIC is recommended for international SEPA transfers',
247
+ btReference: 'BT-86',
248
+ field: 'bic'
249
+ });
250
+ }
251
+ }
252
+
253
+ return results;
254
+ }
255
+
256
+ /**
257
+ * Validate IBAN format and checksum
258
+ */
259
+ private validateIBAN(iban: string): { valid: boolean; message?: string } {
260
+ // Remove spaces and convert to uppercase
261
+ const cleanIBAN = iban.replace(/\s/g, '').toUpperCase();
262
+
263
+ // Check basic format
264
+ if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(cleanIBAN)) {
265
+ return { valid: false, message: 'Invalid IBAN format' };
266
+ }
267
+
268
+ // Get country code
269
+ const countryCode = cleanIBAN.substring(0, 2);
270
+
271
+ // Check country-specific format
272
+ const countryFormat = XRechnungValidator.IBAN_PATTERNS[countryCode];
273
+ if (countryFormat) {
274
+ if (cleanIBAN.length !== countryFormat.length) {
275
+ return {
276
+ valid: false,
277
+ message: `Invalid IBAN length for ${countryCode}: expected ${countryFormat.length}, got ${cleanIBAN.length}`
278
+ };
279
+ }
280
+
281
+ if (!countryFormat.pattern.test(cleanIBAN)) {
282
+ return {
283
+ valid: false,
284
+ message: `Invalid IBAN format for ${countryCode}`
285
+ };
286
+ }
287
+ }
288
+
289
+ // Validate checksum using mod-97 algorithm
290
+ const rearranged = cleanIBAN.substring(4) + cleanIBAN.substring(0, 4);
291
+ const numeric = rearranged.replace(/[A-Z]/g, char => (char.charCodeAt(0) - 55).toString());
292
+
293
+ // Calculate mod 97 for large numbers
294
+ let remainder = 0;
295
+ for (let i = 0; i < numeric.length; i++) {
296
+ remainder = (remainder * 10 + parseInt(numeric[i])) % 97;
297
+ }
298
+
299
+ if (remainder !== 1) {
300
+ return { valid: false, message: 'Invalid IBAN checksum' };
301
+ }
302
+
303
+ return { valid: true };
304
+ }
305
+
306
+ /**
307
+ * Validate BIC format
308
+ */
309
+ private validateBIC(bic: string): { valid: boolean; message?: string } {
310
+ const cleanBIC = bic.replace(/\s/g, '').toUpperCase();
311
+
312
+ if (!XRechnungValidator.BIC_PATTERN.test(cleanBIC)) {
313
+ return {
314
+ valid: false,
315
+ message: 'Invalid BIC format. Expected 8 or 11 alphanumeric characters'
316
+ };
317
+ }
318
+
319
+ // Additional validation could check if BIC exists in SWIFT directory
320
+ // but that requires external data
321
+
322
+ return { valid: true };
323
+ }
324
+
325
+ /**
326
+ * Validate seller contact details
327
+ * Rule: XR-DE-02
328
+ */
329
+ private validateSellerContact(invoice: EInvoice): ValidationResult[] {
330
+ const results: ValidationResult[] = [];
331
+
332
+ // Seller contact is mandatory in XRechnung
333
+ const sellerContact = invoice.metadata?.extensions?.sellerContact as {
334
+ name?: string;
335
+ email?: string;
336
+ phone?: string;
337
+ } | undefined;
338
+
339
+ if (!sellerContact || (!sellerContact.name && !sellerContact.email && !sellerContact.phone)) {
340
+ results.push({
341
+ ruleId: 'XR-DE-02',
342
+ severity: 'error',
343
+ source: 'XRECHNUNG',
344
+ message: 'Seller contact information (name, email, or phone) is mandatory in XRechnung',
345
+ bgReference: 'BG-6',
346
+ field: 'sellerContact'
347
+ });
348
+ }
349
+
350
+ // Validate email format if present
351
+ if (sellerContact?.email && !this.isValidEmail(sellerContact.email)) {
352
+ results.push({
353
+ ruleId: 'XR-DE-02',
354
+ severity: 'warning',
355
+ source: 'XRECHNUNG',
356
+ message: `Invalid email format: ${sellerContact.email}`,
357
+ btReference: 'BT-43',
358
+ field: 'email',
359
+ value: sellerContact.email
360
+ });
361
+ }
362
+
363
+ // Validate phone format if present (basic validation)
364
+ if (sellerContact?.phone && !this.isValidPhone(sellerContact.phone)) {
365
+ results.push({
366
+ ruleId: 'XR-DE-02',
367
+ severity: 'warning',
368
+ source: 'XRECHNUNG',
369
+ message: `Invalid phone format: ${sellerContact.phone}`,
370
+ btReference: 'BT-42',
371
+ field: 'phone',
372
+ value: sellerContact.phone
373
+ });
374
+ }
375
+
376
+ return results;
377
+ }
378
+
379
+ /**
380
+ * Validate email format
381
+ */
382
+ private isValidEmail(email: string): boolean {
383
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
384
+ return emailPattern.test(email);
385
+ }
386
+
387
+ /**
388
+ * Validate phone format (basic)
389
+ */
390
+ private isValidPhone(phone: string): boolean {
391
+ // Remove common formatting characters
392
+ const cleanPhone = phone.replace(/[\s\-\(\)\.]/g, '');
393
+ // Check if it contains only numbers and optional + at start
394
+ return /^\+?[0-9]{6,15}$/.test(cleanPhone);
395
+ }
396
+
397
+ /**
398
+ * Validate tax registration details
399
+ * Rules: XR-DE-03, XR-DE-04
400
+ */
401
+ private validateTaxRegistration(invoice: EInvoice): ValidationResult[] {
402
+ const results: ValidationResult[] = [];
403
+
404
+ const sellerVatId = invoice.metadata?.sellerTaxId ||
405
+ (invoice.from?.type === 'company' ? (invoice.from as any).registrationDetails?.vatId : undefined) ||
406
+ invoice.metadata?.extensions?.sellerVatId;
407
+ const sellerTaxId = invoice.metadata?.extensions?.sellerTaxId;
408
+
409
+ // Either VAT ID or Tax ID must be present
410
+ if (!sellerVatId && !sellerTaxId) {
411
+ results.push({
412
+ ruleId: 'XR-DE-03',
413
+ severity: 'error',
414
+ source: 'XRECHNUNG',
415
+ message: 'Either seller VAT ID (BT-31) or Tax ID (BT-32) must be provided',
416
+ btReference: 'BT-31',
417
+ field: 'sellerTaxRegistration'
418
+ });
419
+ }
420
+
421
+ // Validate German VAT ID format if present
422
+ if (sellerVatId && sellerVatId.startsWith('DE')) {
423
+ if (!this.isValidGermanVatId(sellerVatId)) {
424
+ results.push({
425
+ ruleId: 'XR-DE-04',
426
+ severity: 'error',
427
+ source: 'XRECHNUNG',
428
+ message: `Invalid German VAT ID format: ${sellerVatId}`,
429
+ btReference: 'BT-31',
430
+ field: 'vatId',
431
+ value: sellerVatId
432
+ });
433
+ }
434
+ }
435
+
436
+ // Validate German Tax ID format if present
437
+ if (sellerTaxId && this.looksLikeGermanTaxId(sellerTaxId)) {
438
+ if (!this.isValidGermanTaxId(sellerTaxId)) {
439
+ results.push({
440
+ ruleId: 'XR-DE-04',
441
+ severity: 'warning',
442
+ source: 'XRECHNUNG',
443
+ message: `Invalid German Tax ID format: ${sellerTaxId}`,
444
+ btReference: 'BT-32',
445
+ field: 'taxId',
446
+ value: sellerTaxId
447
+ });
448
+ }
449
+ }
450
+
451
+ return results;
452
+ }
453
+
454
+ /**
455
+ * Validate German VAT ID format
456
+ */
457
+ private isValidGermanVatId(vatId: string): boolean {
458
+ // German VAT ID: DE followed by 9 digits
459
+ const germanVatPattern = /^DE[0-9]{9}$/;
460
+ return germanVatPattern.test(vatId.replace(/\s/g, ''));
461
+ }
462
+
463
+ /**
464
+ * Check if value looks like a German Tax ID
465
+ */
466
+ private looksLikeGermanTaxId(value: string): boolean {
467
+ const clean = value.replace(/[\s\/\-]/g, '');
468
+ return /^[0-9]{10,11}$/.test(clean);
469
+ }
470
+
471
+ /**
472
+ * Validate German Tax ID format
473
+ */
474
+ private isValidGermanTaxId(taxId: string): boolean {
475
+ // German Tax ID: 11 digits with specific checksum algorithm
476
+ const clean = taxId.replace(/[\s\/\-]/g, '');
477
+
478
+ if (!/^[0-9]{11}$/.test(clean)) {
479
+ return false;
480
+ }
481
+
482
+ // Simplified validation - full algorithm would require checksum calculation
483
+ // At least check that not all digits are the same
484
+ const firstDigit = clean[0];
485
+ return !clean.split('').every(digit => digit === firstDigit);
486
+ }
487
+
488
+ /**
489
+ * Create XRechnung profile validator instance
490
+ */
491
+ static create(): XRechnungValidator {
492
+ return new XRechnungValidator();
493
+ }
494
+ }
@@ -44,6 +44,7 @@ export interface ValidationError {
44
44
  export interface ValidationResult {
45
45
  valid: boolean; // Overall validation result
46
46
  errors: ValidationError[]; // List of validation errors
47
+ warnings?: ValidationError[]; // List of validation warnings (optional)
47
48
  level: ValidationLevel; // The level that was validated
48
49
  }
49
50
 
@@ -0,0 +1,104 @@
1
+ /**
2
+ * EN16931-compliant metadata interface for EInvoice
3
+ * Contains all additional fields required for full standards compliance
4
+ */
5
+
6
+ import type { business } from '@tsclass/tsclass';
7
+ import type { InvoiceFormat } from './common.js';
8
+
9
+ /**
10
+ * Extended metadata for EN16931 compliance
11
+ */
12
+ export interface IEInvoiceMetadata {
13
+ // Format identification
14
+ format?: InvoiceFormat;
15
+ version?: string;
16
+ profile?: string;
17
+ customizationId?: string;
18
+
19
+ // EN16931 Business Terms
20
+ vatAccountingCurrency?: string; // BT-6
21
+ documentTypeCode?: string; // BT-3
22
+ paymentMeansCode?: string; // BT-81
23
+ paidAmount?: number; // BT-113
24
+ amountDue?: number; // BT-115
25
+
26
+ // Tax identifiers
27
+ sellerTaxId?: string; // BT-31
28
+ buyerTaxId?: string; // BT-48
29
+ buyerReference?: string; // BT-10
30
+ profileId?: string; // BT-23
31
+ paymentTerms?: string; // BT-20
32
+
33
+ // Delivery information (BG-13)
34
+ deliveryAddress?: {
35
+ streetName?: string;
36
+ houseNumber?: string;
37
+ city?: string;
38
+ postalCode?: string;
39
+ countryCode?: string; // BT-80
40
+ countrySubdivision?: string;
41
+ };
42
+
43
+ // Payment information (BG-16)
44
+ paymentAccount?: {
45
+ iban?: string; // BT-84
46
+ accountName?: string; // BT-85
47
+ bankId?: string; // BT-86
48
+ };
49
+
50
+ // Allowances and charges (BG-20, BG-21)
51
+ allowances?: Array<{
52
+ amount: number; // BT-92
53
+ baseAmount?: number; // BT-93
54
+ percentage?: number; // BT-94
55
+ vatCategoryCode?: string; // BT-95
56
+ vatRate?: number; // BT-96
57
+ reason?: string; // BT-97
58
+ reasonCode?: string; // BT-98
59
+ }>;
60
+
61
+ charges?: Array<{
62
+ amount: number; // BT-99
63
+ baseAmount?: number; // BT-100
64
+ percentage?: number; // BT-101
65
+ vatCategoryCode?: string; // BT-102
66
+ vatRate?: number; // BT-103
67
+ reason?: string; // BT-104
68
+ reasonCode?: string; // BT-105
69
+ }>;
70
+
71
+ // Extensions for specific standards
72
+ extensions?: Record<string, any>;
73
+ }
74
+
75
+ /**
76
+ * Extended item metadata for EN16931 compliance
77
+ */
78
+ export interface IItemMetadata {
79
+ vatCategoryCode?: string; // BT-151
80
+ priceBaseQuantity?: number; // BT-149
81
+ exemptionReason?: string; // BT-120 (for exempt categories)
82
+ originCountryCode?: string; // BT-159
83
+ commodityCode?: string; // BT-158
84
+
85
+ // Item attributes (BG-32)
86
+ attributes?: Array<{
87
+ name: string; // BT-160
88
+ value: string; // BT-161
89
+ }>;
90
+ }
91
+
92
+ /**
93
+ * Extended accounting document item with metadata
94
+ */
95
+ export interface IExtendedAccountingDocItem {
96
+ position: number;
97
+ name: string;
98
+ articleNumber?: string;
99
+ unitType: string;
100
+ unitQuantity: number;
101
+ unitNetPrice: number;
102
+ vatPercentage: number;
103
+ metadata?: IItemMetadata;
104
+ }
package/ts/plugins.ts CHANGED
@@ -22,8 +22,12 @@ import {
22
22
 
23
23
  // XML-related imports
24
24
  import { DOMParser, XMLSerializer } from 'xmldom';
25
+ import * as xmldom from 'xmldom';
25
26
  import * as xpath from 'xpath';
26
27
 
28
+ // XSLT/Schematron imports
29
+ import * as SaxonJS from 'saxon-js';
30
+
27
31
  // Compression-related imports
28
32
  import * as pako from 'pako';
29
33
 
@@ -49,8 +53,12 @@ export {
49
53
  // XML-related exports
50
54
  DOMParser,
51
55
  XMLSerializer,
56
+ xmldom,
52
57
  xpath,
53
58
 
59
+ // XSLT/Schematron exports
60
+ SaxonJS,
61
+
54
62
  // Compression-related exports
55
63
  pako,
56
64
 
@@ -0,0 +1,3 @@
1
+ {
2
+ "order": 1
3
+ }
@@ -1,40 +0,0 @@
1
- import * as plugins from './plugins.js';
2
- /**
3
- * A class to convert a given XML string (ZUGFeRD/Factur-X, UBL or fatturaPA)
4
- * into a structured ILetter with invoice data.
5
- *
6
- * Handles different invoice XML formats:
7
- * - ZUGFeRD/Factur-X (CII)
8
- * - UBL
9
- * - FatturaPA
10
- */
11
- export declare class FacturXDecoder {
12
- private xmlString;
13
- private xmlFormat;
14
- private xmlDoc;
15
- constructor(xmlString: string);
16
- /**
17
- * Detects the XML invoice format using simple string checks
18
- */
19
- private detectFormat;
20
- /**
21
- * Extracts text from the first element matching the XPath-like selector
22
- */
23
- private getElementText;
24
- /**
25
- * Converts XML to a structured letter object
26
- */
27
- getLetterData(): Promise<plugins.tsclass.business.ILetter>;
28
- /**
29
- * Parse CII (ZUGFeRD/Factur-X) formatted XML
30
- */
31
- private parseCII;
32
- /**
33
- * Parse generic XML using default approach
34
- */
35
- private parseGeneric;
36
- /**
37
- * Creates a default letter object with minimal data
38
- */
39
- private createDefaultLetter;
40
- }