@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,579 @@
1
+ /**
2
+ * Factur-X validator for profile-specific compliance
3
+ * Implements validation for MINIMUM, BASIC, EN16931, and EXTENDED profiles
4
+ */
5
+
6
+ import type { ValidationResult } from './validation.types.js';
7
+ import type { EInvoice } from '../../einvoice.js';
8
+
9
+ /**
10
+ * Factur-X Profile definitions
11
+ */
12
+ export enum FacturXProfile {
13
+ MINIMUM = 'MINIMUM',
14
+ BASIC = 'BASIC',
15
+ BASIC_WL = 'BASIC_WL', // Basic without lines
16
+ EN16931 = 'EN16931',
17
+ EXTENDED = 'EXTENDED'
18
+ }
19
+
20
+ /**
21
+ * Field cardinality requirements per profile
22
+ */
23
+ interface ProfileRequirements {
24
+ mandatory: string[];
25
+ optional: string[];
26
+ forbidden?: string[];
27
+ }
28
+
29
+ /**
30
+ * Factur-X Validator
31
+ * Validates invoices according to Factur-X profile specifications
32
+ */
33
+ export class FacturXValidator {
34
+ private static instance: FacturXValidator;
35
+
36
+ /**
37
+ * Profile requirements mapping
38
+ */
39
+ private profileRequirements: Record<FacturXProfile, ProfileRequirements> = {
40
+ [FacturXProfile.MINIMUM]: {
41
+ mandatory: [
42
+ 'accountingDocId', // BT-1: Invoice number
43
+ 'issueDate', // BT-2: Invoice issue date
44
+ 'accountingDocType', // BT-3: Invoice type code
45
+ 'currency', // BT-5: Invoice currency code
46
+ 'from.name', // BT-27: Seller name
47
+ 'from.vatNumber', // BT-31: Seller VAT identifier
48
+ 'to.name', // BT-44: Buyer name
49
+ 'totalInvoiceAmount', // BT-112: Invoice total amount with VAT
50
+ 'totalNetAmount', // BT-109: Invoice total amount without VAT
51
+ 'totalVatAmount', // BT-110: Invoice total VAT amount
52
+ ],
53
+ optional: []
54
+ },
55
+
56
+ [FacturXProfile.BASIC]: {
57
+ mandatory: [
58
+ // All MINIMUM fields plus:
59
+ 'accountingDocId',
60
+ 'issueDate',
61
+ 'accountingDocType',
62
+ 'currency',
63
+ 'from.name',
64
+ 'from.vatNumber',
65
+ 'from.address', // BT-35: Seller postal address
66
+ 'from.country', // BT-40: Seller country code
67
+ 'to.name',
68
+ 'to.address', // BT-50: Buyer postal address
69
+ 'to.country', // BT-55: Buyer country code
70
+ 'items', // BG-25: Invoice line items
71
+ 'items[].name', // BT-153: Item name
72
+ 'items[].unitQuantity', // BT-129: Invoiced quantity
73
+ 'items[].unitNetPrice', // BT-146: Item net price
74
+ 'items[].vatPercentage', // BT-152: Invoiced item VAT rate
75
+ 'totalInvoiceAmount',
76
+ 'totalNetAmount',
77
+ 'totalVatAmount',
78
+ 'dueDate', // BT-9: Payment due date
79
+ ],
80
+ optional: [
81
+ 'metadata.buyerReference', // BT-10: Buyer reference
82
+ 'metadata.purchaseOrderReference', // BT-13: Purchase order reference
83
+ 'metadata.salesOrderReference', // BT-14: Sales order reference
84
+ 'metadata.contractReference', // BT-12: Contract reference
85
+ 'projectReference', // BT-11: Project reference
86
+ ]
87
+ },
88
+
89
+ [FacturXProfile.BASIC_WL]: {
90
+ // Basic without lines - for summary invoices
91
+ mandatory: [
92
+ 'accountingDocId',
93
+ 'issueDate',
94
+ 'accountingDocType',
95
+ 'currency',
96
+ 'from.name',
97
+ 'from.vatNumber',
98
+ 'from.address',
99
+ 'from.country',
100
+ 'to.name',
101
+ 'to.address',
102
+ 'to.country',
103
+ 'totalInvoiceAmount',
104
+ 'totalNetAmount',
105
+ 'totalVatAmount',
106
+ 'dueDate',
107
+ // No items required
108
+ ],
109
+ optional: [
110
+ 'metadata.buyerReference',
111
+ 'metadata.purchaseOrderReference',
112
+ 'metadata.contractReference',
113
+ ]
114
+ },
115
+
116
+ [FacturXProfile.EN16931]: {
117
+ // Full EN16931 compliance - all mandatory fields from the standard
118
+ mandatory: [
119
+ // Document level
120
+ 'accountingDocId',
121
+ 'issueDate',
122
+ 'accountingDocType',
123
+ 'currency',
124
+ 'metadata.buyerReference',
125
+
126
+ // Seller information
127
+ 'from.name',
128
+ 'from.address',
129
+ 'from.city',
130
+ 'from.postalCode',
131
+ 'from.country',
132
+ 'from.vatNumber',
133
+
134
+ // Buyer information
135
+ 'to.name',
136
+ 'to.address',
137
+ 'to.city',
138
+ 'to.postalCode',
139
+ 'to.country',
140
+
141
+ // Line items
142
+ 'items',
143
+ 'items[].name',
144
+ 'items[].unitQuantity',
145
+ 'items[].unitType',
146
+ 'items[].unitNetPrice',
147
+ 'items[].vatPercentage',
148
+
149
+ // Totals
150
+ 'totalInvoiceAmount',
151
+ 'totalNetAmount',
152
+ 'totalVatAmount',
153
+ 'dueDate',
154
+ ],
155
+ optional: [
156
+ // All other EN16931 fields
157
+ 'metadata.purchaseOrderReference',
158
+ 'metadata.salesOrderReference',
159
+ 'metadata.contractReference',
160
+ 'metadata.deliveryDate',
161
+ 'metadata.paymentTerms',
162
+ 'metadata.paymentMeans',
163
+ 'to.vatNumber',
164
+ 'to.legalRegistration',
165
+ 'items[].articleNumber',
166
+ 'items[].description',
167
+ 'paymentAccount',
168
+ ]
169
+ },
170
+
171
+ [FacturXProfile.EXTENDED]: {
172
+ // Extended profile allows all fields
173
+ mandatory: [
174
+ // Same as EN16931 core
175
+ 'accountingDocId',
176
+ 'issueDate',
177
+ 'accountingDocType',
178
+ 'currency',
179
+ 'from.name',
180
+ 'from.vatNumber',
181
+ 'to.name',
182
+ 'totalInvoiceAmount',
183
+ ],
184
+ optional: [
185
+ // All fields are allowed in EXTENDED profile
186
+ ]
187
+ }
188
+ };
189
+
190
+ /**
191
+ * Singleton pattern for validator instance
192
+ */
193
+ public static create(): FacturXValidator {
194
+ if (!FacturXValidator.instance) {
195
+ FacturXValidator.instance = new FacturXValidator();
196
+ }
197
+ return FacturXValidator.instance;
198
+ }
199
+
200
+ /**
201
+ * Main validation entry point for Factur-X
202
+ */
203
+ public validateFacturX(invoice: EInvoice, profile?: FacturXProfile): ValidationResult[] {
204
+ const results: ValidationResult[] = [];
205
+
206
+ // Detect profile if not provided
207
+ const detectedProfile = profile || this.detectProfile(invoice);
208
+
209
+ // Skip if not a Factur-X invoice
210
+ if (!detectedProfile) {
211
+ return results;
212
+ }
213
+
214
+ // Validate according to profile
215
+ results.push(...this.validateProfileRequirements(invoice, detectedProfile));
216
+ results.push(...this.validateProfileSpecificRules(invoice, detectedProfile));
217
+
218
+ // Add profile-specific business rules
219
+ if (detectedProfile === FacturXProfile.MINIMUM) {
220
+ results.push(...this.validateMinimumProfile(invoice));
221
+ } else if (detectedProfile === FacturXProfile.BASIC || detectedProfile === FacturXProfile.BASIC_WL) {
222
+ results.push(...this.validateBasicProfile(invoice, detectedProfile));
223
+ } else if (detectedProfile === FacturXProfile.EN16931) {
224
+ results.push(...this.validateEN16931Profile(invoice));
225
+ } else if (detectedProfile === FacturXProfile.EXTENDED) {
226
+ results.push(...this.validateExtendedProfile(invoice));
227
+ }
228
+
229
+ return results;
230
+ }
231
+
232
+ /**
233
+ * Detect Factur-X profile from invoice metadata
234
+ */
235
+ public detectProfile(invoice: EInvoice): FacturXProfile | null {
236
+ const profileId = invoice.metadata?.profileId || '';
237
+ const customizationId = invoice.metadata?.customizationId || '';
238
+ const format = invoice.metadata?.format;
239
+
240
+ // Check if it's a Factur-X invoice
241
+ if (!format?.includes('facturx') && !profileId.includes('facturx') &&
242
+ !customizationId.includes('facturx') && !profileId.includes('zugferd')) {
243
+ return null;
244
+ }
245
+
246
+ // Detect specific profile
247
+ const profileLower = profileId.toLowerCase();
248
+ const customLower = customizationId.toLowerCase();
249
+
250
+ if (profileLower.includes('minimum') || customLower.includes('minimum')) {
251
+ return FacturXProfile.MINIMUM;
252
+ } else if (profileLower.includes('basic_wl') || customLower.includes('basicwl')) {
253
+ return FacturXProfile.BASIC_WL;
254
+ } else if (profileLower.includes('basic') || customLower.includes('basic')) {
255
+ return FacturXProfile.BASIC;
256
+ } else if (profileLower.includes('en16931') || customLower.includes('en16931') ||
257
+ profileLower.includes('comfort') || customLower.includes('comfort')) {
258
+ return FacturXProfile.EN16931;
259
+ } else if (profileLower.includes('extended') || customLower.includes('extended')) {
260
+ return FacturXProfile.EXTENDED;
261
+ }
262
+
263
+ // Default to BASIC if format is Factur-X but profile unclear
264
+ return FacturXProfile.BASIC;
265
+ }
266
+
267
+ /**
268
+ * Validate field requirements for a specific profile
269
+ */
270
+ private validateProfileRequirements(invoice: EInvoice, profile: FacturXProfile): ValidationResult[] {
271
+ const results: ValidationResult[] = [];
272
+ const requirements = this.profileRequirements[profile];
273
+
274
+ // Check mandatory fields
275
+ for (const field of requirements.mandatory) {
276
+ const value = this.getFieldValue(invoice, field);
277
+ if (value === undefined || value === null || value === '') {
278
+ results.push({
279
+ ruleId: `FX-${profile}-M01`,
280
+ severity: 'error',
281
+ message: `Field '${field}' is mandatory for Factur-X ${profile} profile`,
282
+ field: field,
283
+ source: 'FACTURX'
284
+ });
285
+ }
286
+ }
287
+
288
+ // Check forbidden fields (if any)
289
+ if (requirements.forbidden) {
290
+ for (const field of requirements.forbidden) {
291
+ const value = this.getFieldValue(invoice, field);
292
+ if (value !== undefined && value !== null) {
293
+ results.push({
294
+ ruleId: `FX-${profile}-F01`,
295
+ severity: 'error',
296
+ message: `Field '${field}' is not allowed in Factur-X ${profile} profile`,
297
+ field: field,
298
+ value: value,
299
+ source: 'FACTURX'
300
+ });
301
+ }
302
+ }
303
+ }
304
+
305
+ return results;
306
+ }
307
+
308
+ /**
309
+ * Get field value from invoice using dot notation
310
+ */
311
+ private getFieldValue(invoice: any, fieldPath: string): any {
312
+ // Handle special calculated fields
313
+ if (fieldPath === 'totalInvoiceAmount') {
314
+ return invoice.totalGross || invoice.totalInvoiceAmount;
315
+ }
316
+ if (fieldPath === 'totalNetAmount') {
317
+ return invoice.totalNet || invoice.totalNetAmount;
318
+ }
319
+ if (fieldPath === 'totalVatAmount') {
320
+ return invoice.totalVat || invoice.totalVatAmount;
321
+ }
322
+ if (fieldPath === 'dueDate') {
323
+ // Check for dueInDays which is used in EInvoice
324
+ if (invoice.dueInDays !== undefined && invoice.dueInDays !== null) {
325
+ return true; // Has payment terms
326
+ }
327
+ return invoice.dueDate;
328
+ }
329
+
330
+ const parts = fieldPath.split('.');
331
+ let value = invoice;
332
+
333
+ for (const part of parts) {
334
+ if (part.includes('[')) {
335
+ // Array field like items[]
336
+ const fieldName = part.substring(0, part.indexOf('['));
337
+ const arrayField = part.substring(part.indexOf('[') + 1, part.indexOf(']'));
338
+
339
+ if (!value[fieldName] || !Array.isArray(value[fieldName])) {
340
+ return undefined;
341
+ }
342
+
343
+ if (arrayField === '') {
344
+ // Check if array exists and has items
345
+ return value[fieldName].length > 0 ? value[fieldName] : undefined;
346
+ } else {
347
+ // Check specific field in array items
348
+ return value[fieldName].every((item: any) => item[arrayField] !== undefined);
349
+ }
350
+ } else {
351
+ value = value?.[part];
352
+ }
353
+ }
354
+
355
+ return value;
356
+ }
357
+
358
+ /**
359
+ * Profile-specific validation rules
360
+ */
361
+ private validateProfileSpecificRules(invoice: EInvoice, profile: FacturXProfile): ValidationResult[] {
362
+ const results: ValidationResult[] = [];
363
+
364
+ // Validate according to profile level
365
+ switch (profile) {
366
+ case FacturXProfile.MINIMUM:
367
+ // MINIMUM requires at least gross amounts
368
+ // Check both calculated totals and direct properties (for test compatibility)
369
+ const totalGross = invoice.totalGross || (invoice as any).totalInvoiceAmount;
370
+ if (!totalGross || totalGross <= 0) {
371
+ results.push({
372
+ ruleId: 'FX-MIN-01',
373
+ severity: 'error',
374
+ message: 'MINIMUM profile requires positive total invoice amount',
375
+ field: 'totalInvoiceAmount',
376
+ value: totalGross,
377
+ source: 'FACTURX'
378
+ });
379
+ }
380
+ break;
381
+
382
+ case FacturXProfile.BASIC:
383
+ case FacturXProfile.BASIC_WL:
384
+ // BASIC requires VAT breakdown
385
+ const totalVat = invoice.totalVat;
386
+ if (!invoice.metadata?.extensions?.taxDetails && totalVat > 0) {
387
+ results.push({
388
+ ruleId: 'FX-BAS-01',
389
+ severity: 'warning',
390
+ message: 'BASIC profile should include VAT breakdown when VAT is present',
391
+ field: 'metadata.extensions.taxDetails',
392
+ source: 'FACTURX'
393
+ });
394
+ }
395
+ break;
396
+
397
+ case FacturXProfile.EN16931:
398
+ // EN16931 requires full compliance - additional checks handled by EN16931 validator
399
+ if (!invoice.metadata?.buyerReference && !invoice.metadata?.extensions?.purchaseOrderReference) {
400
+ results.push({
401
+ ruleId: 'FX-EN-01',
402
+ severity: 'error',
403
+ message: 'EN16931 profile requires either buyer reference or purchase order reference',
404
+ field: 'metadata.buyerReference',
405
+ source: 'FACTURX'
406
+ });
407
+ }
408
+ break;
409
+ }
410
+
411
+ return results;
412
+ }
413
+
414
+ /**
415
+ * Validate MINIMUM profile specific rules
416
+ */
417
+ private validateMinimumProfile(invoice: EInvoice): ValidationResult[] {
418
+ const results: ValidationResult[] = [];
419
+
420
+ // MINIMUM profile allows only essential fields
421
+ // Check that complex structures are not present
422
+ if (invoice.items && invoice.items.length > 0) {
423
+ // Lines are optional but if present must be minimal
424
+ invoice.items.forEach((item, index) => {
425
+ if ((item as any).allowances || (item as any).charges) {
426
+ results.push({
427
+ ruleId: 'FX-MIN-02',
428
+ severity: 'warning',
429
+ message: `Line ${index + 1}: MINIMUM profile should not include line allowances/charges`,
430
+ field: `items[${index}]`,
431
+ source: 'FACTURX'
432
+ });
433
+ }
434
+ });
435
+ }
436
+
437
+ return results;
438
+ }
439
+
440
+ /**
441
+ * Validate BASIC profile specific rules
442
+ */
443
+ private validateBasicProfile(invoice: EInvoice, profile: FacturXProfile): ValidationResult[] {
444
+ const results: ValidationResult[] = [];
445
+
446
+ // BASIC requires line items (except BASIC_WL)
447
+ // Only check for line items in BASIC profile, not BASIC_WL
448
+ if (profile === FacturXProfile.BASIC) {
449
+ if (!invoice.items || invoice.items.length === 0) {
450
+ results.push({
451
+ ruleId: 'FX-BAS-02',
452
+ severity: 'error',
453
+ message: 'BASIC profile requires at least one invoice line item',
454
+ field: 'items',
455
+ source: 'FACTURX'
456
+ });
457
+ }
458
+ }
459
+
460
+ // Payment information should be present
461
+ if (!invoice.dueInDays && invoice.dueInDays !== 0) {
462
+ results.push({
463
+ ruleId: 'FX-BAS-03',
464
+ severity: 'warning',
465
+ message: 'BASIC profile should include payment terms (due in days)',
466
+ field: 'dueInDays',
467
+ source: 'FACTURX'
468
+ });
469
+ }
470
+
471
+ return results;
472
+ }
473
+
474
+ /**
475
+ * Validate EN16931 profile specific rules
476
+ */
477
+ private validateEN16931Profile(invoice: EInvoice): ValidationResult[] {
478
+ const results: ValidationResult[] = [];
479
+
480
+ // EN16931 requires complete address information
481
+ const fromAny = invoice.from as any;
482
+ const toAny = invoice.to as any;
483
+
484
+ if (!fromAny?.city || !fromAny?.postalCode) {
485
+ results.push({
486
+ ruleId: 'FX-EN-02',
487
+ severity: 'error',
488
+ message: 'EN16931 profile requires complete seller address including city and postal code',
489
+ field: 'from.address',
490
+ source: 'FACTURX'
491
+ });
492
+ }
493
+
494
+ if (!toAny?.city || !toAny?.postalCode) {
495
+ results.push({
496
+ ruleId: 'FX-EN-03',
497
+ severity: 'error',
498
+ message: 'EN16931 profile requires complete buyer address including city and postal code',
499
+ field: 'to.address',
500
+ source: 'FACTURX'
501
+ });
502
+ }
503
+
504
+ // Line items must have unit type
505
+ if (invoice.items) {
506
+ invoice.items.forEach((item, index) => {
507
+ if (!item.unitType) {
508
+ results.push({
509
+ ruleId: 'FX-EN-04',
510
+ severity: 'error',
511
+ message: `Line ${index + 1}: EN16931 profile requires unit of measure`,
512
+ field: `items[${index}].unitType`,
513
+ source: 'FACTURX'
514
+ });
515
+ }
516
+ });
517
+ }
518
+
519
+ return results;
520
+ }
521
+
522
+ /**
523
+ * Validate EXTENDED profile specific rules
524
+ */
525
+ private validateExtendedProfile(invoice: EInvoice): ValidationResult[] {
526
+ const results: ValidationResult[] = [];
527
+
528
+ // EXTENDED profile is most permissive - mainly check for data consistency
529
+ if (invoice.metadata?.extensions) {
530
+ // Extended profile can include additional structured data
531
+ // Validate that extended data is well-formed
532
+ const extensions = invoice.metadata.extensions;
533
+
534
+ if (extensions.attachments && Array.isArray(extensions.attachments)) {
535
+ extensions.attachments.forEach((attachment: any, index: number) => {
536
+ if (!attachment.filename || !attachment.mimeType) {
537
+ results.push({
538
+ ruleId: 'FX-EXT-01',
539
+ severity: 'warning',
540
+ message: `Attachment ${index + 1}: Should include filename and MIME type`,
541
+ field: `metadata.extensions.attachments[${index}]`,
542
+ source: 'FACTURX'
543
+ });
544
+ }
545
+ });
546
+ }
547
+ }
548
+
549
+ return results;
550
+ }
551
+
552
+ /**
553
+ * Get profile display name
554
+ */
555
+ public getProfileDisplayName(profile: FacturXProfile): string {
556
+ const names: Record<FacturXProfile, string> = {
557
+ [FacturXProfile.MINIMUM]: 'Factur-X MINIMUM',
558
+ [FacturXProfile.BASIC]: 'Factur-X BASIC',
559
+ [FacturXProfile.BASIC_WL]: 'Factur-X BASIC WL',
560
+ [FacturXProfile.EN16931]: 'Factur-X EN16931',
561
+ [FacturXProfile.EXTENDED]: 'Factur-X EXTENDED'
562
+ };
563
+ return names[profile];
564
+ }
565
+
566
+ /**
567
+ * Get profile compliance level (for reporting)
568
+ */
569
+ public getProfileComplianceLevel(profile: FacturXProfile): number {
570
+ const levels: Record<FacturXProfile, number> = {
571
+ [FacturXProfile.MINIMUM]: 1,
572
+ [FacturXProfile.BASIC_WL]: 2,
573
+ [FacturXProfile.BASIC]: 3,
574
+ [FacturXProfile.EN16931]: 4,
575
+ [FacturXProfile.EXTENDED]: 5
576
+ };
577
+ return levels[profile];
578
+ }
579
+ }