@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,654 @@
1
+ /**
2
+ * Semantic Model Validator
3
+ * Validates invoices against EN16931 Business Terms and Business Groups
4
+ */
5
+
6
+ import type { ValidationResult } from '../validation/validation.types.js';
7
+ import type { EN16931SemanticModel, BusinessTerms, BusinessGroups } from './bt-bg.model.js';
8
+ import type { EInvoice } from '../../einvoice.js';
9
+ import { SemanticModelAdapter } from './semantic.adapter.js';
10
+
11
+ /**
12
+ * Business Term validation rules
13
+ */
14
+ interface BTValidationRule {
15
+ btId: string;
16
+ description: string;
17
+ mandatory: boolean;
18
+ validate: (model: EN16931SemanticModel) => ValidationResult | null;
19
+ }
20
+
21
+ /**
22
+ * Semantic Model Validator
23
+ * Validates against all EN16931 Business Terms (BT) and Business Groups (BG)
24
+ */
25
+ export class SemanticModelValidator {
26
+ private adapter: SemanticModelAdapter;
27
+ private btRules: BTValidationRule[];
28
+
29
+ constructor() {
30
+ this.adapter = new SemanticModelAdapter();
31
+ this.btRules = this.initializeBusinessTermRules();
32
+ }
33
+
34
+ /**
35
+ * Validate an invoice using the semantic model
36
+ */
37
+ public validate(invoice: EInvoice): ValidationResult[] {
38
+ const results: ValidationResult[] = [];
39
+
40
+ // Convert to semantic model
41
+ const model = this.adapter.toSemanticModel(invoice);
42
+
43
+ // Validate all business terms
44
+ for (const rule of this.btRules) {
45
+ const result = rule.validate(model);
46
+ if (result) {
47
+ results.push(result);
48
+ }
49
+ }
50
+
51
+ // Validate business groups
52
+ results.push(...this.validateBusinessGroups(model));
53
+
54
+ // Validate cardinality constraints
55
+ results.push(...this.validateCardinality(model));
56
+
57
+ // Validate conditional rules
58
+ results.push(...this.validateConditionalRules(model));
59
+
60
+ return results;
61
+ }
62
+
63
+ /**
64
+ * Initialize Business Term validation rules
65
+ */
66
+ private initializeBusinessTermRules(): BTValidationRule[] {
67
+ return [
68
+ // Document level mandatory fields
69
+ {
70
+ btId: 'BT-1',
71
+ description: 'Invoice number',
72
+ mandatory: true,
73
+ validate: (model) => {
74
+ if (!model.documentInformation.invoiceNumber) {
75
+ return {
76
+ ruleId: 'BT-1',
77
+ severity: 'error',
78
+ message: 'Invoice number is mandatory',
79
+ field: 'documentInformation.invoiceNumber',
80
+ btReference: 'BT-1',
81
+ source: 'SEMANTIC'
82
+ };
83
+ }
84
+ return null;
85
+ }
86
+ },
87
+ {
88
+ btId: 'BT-2',
89
+ description: 'Invoice issue date',
90
+ mandatory: true,
91
+ validate: (model) => {
92
+ if (!model.documentInformation.issueDate) {
93
+ return {
94
+ ruleId: 'BT-2',
95
+ severity: 'error',
96
+ message: 'Invoice issue date is mandatory',
97
+ field: 'documentInformation.issueDate',
98
+ btReference: 'BT-2',
99
+ source: 'SEMANTIC'
100
+ };
101
+ }
102
+ return null;
103
+ }
104
+ },
105
+ {
106
+ btId: 'BT-3',
107
+ description: 'Invoice type code',
108
+ mandatory: true,
109
+ validate: (model) => {
110
+ if (!model.documentInformation.typeCode) {
111
+ return {
112
+ ruleId: 'BT-3',
113
+ severity: 'error',
114
+ message: 'Invoice type code is mandatory',
115
+ field: 'documentInformation.typeCode',
116
+ btReference: 'BT-3',
117
+ source: 'SEMANTIC'
118
+ };
119
+ }
120
+ const validCodes = ['380', '381', '383', '384', '386', '389'];
121
+ if (!validCodes.includes(model.documentInformation.typeCode)) {
122
+ return {
123
+ ruleId: 'BT-3',
124
+ severity: 'error',
125
+ message: `Invalid invoice type code. Must be one of: ${validCodes.join(', ')}`,
126
+ field: 'documentInformation.typeCode',
127
+ value: model.documentInformation.typeCode,
128
+ btReference: 'BT-3',
129
+ source: 'SEMANTIC'
130
+ };
131
+ }
132
+ return null;
133
+ }
134
+ },
135
+ {
136
+ btId: 'BT-5',
137
+ description: 'Invoice currency code',
138
+ mandatory: true,
139
+ validate: (model) => {
140
+ if (!model.documentInformation.currencyCode) {
141
+ return {
142
+ ruleId: 'BT-5',
143
+ severity: 'error',
144
+ message: 'Invoice currency code is mandatory',
145
+ field: 'documentInformation.currencyCode',
146
+ btReference: 'BT-5',
147
+ source: 'SEMANTIC'
148
+ };
149
+ }
150
+ // Validate ISO 4217 currency code
151
+ if (!/^[A-Z]{3}$/.test(model.documentInformation.currencyCode)) {
152
+ return {
153
+ ruleId: 'BT-5',
154
+ severity: 'error',
155
+ message: 'Currency code must be a valid ISO 4217 code',
156
+ field: 'documentInformation.currencyCode',
157
+ value: model.documentInformation.currencyCode,
158
+ btReference: 'BT-5',
159
+ source: 'SEMANTIC'
160
+ };
161
+ }
162
+ return null;
163
+ }
164
+ },
165
+
166
+ // Seller mandatory fields
167
+ {
168
+ btId: 'BT-27',
169
+ description: 'Seller name',
170
+ mandatory: true,
171
+ validate: (model) => {
172
+ if (!model.seller?.name) {
173
+ return {
174
+ ruleId: 'BT-27',
175
+ severity: 'error',
176
+ message: 'Seller name is mandatory',
177
+ field: 'seller.name',
178
+ btReference: 'BT-27',
179
+ source: 'SEMANTIC'
180
+ };
181
+ }
182
+ return null;
183
+ }
184
+ },
185
+ {
186
+ btId: 'BT-40',
187
+ description: 'Seller country code',
188
+ mandatory: true,
189
+ validate: (model) => {
190
+ if (!model.seller?.postalAddress?.countryCode) {
191
+ return {
192
+ ruleId: 'BT-40',
193
+ severity: 'error',
194
+ message: 'Seller country code is mandatory',
195
+ field: 'seller.postalAddress.countryCode',
196
+ btReference: 'BT-40',
197
+ source: 'SEMANTIC'
198
+ };
199
+ }
200
+ // Validate ISO 3166-1 alpha-2 country code
201
+ if (!/^[A-Z]{2}$/.test(model.seller.postalAddress.countryCode)) {
202
+ return {
203
+ ruleId: 'BT-40',
204
+ severity: 'error',
205
+ message: 'Country code must be a valid ISO 3166-1 alpha-2 code',
206
+ field: 'seller.postalAddress.countryCode',
207
+ value: model.seller.postalAddress.countryCode,
208
+ btReference: 'BT-40',
209
+ source: 'SEMANTIC'
210
+ };
211
+ }
212
+ return null;
213
+ }
214
+ },
215
+
216
+ // Buyer mandatory fields
217
+ {
218
+ btId: 'BT-44',
219
+ description: 'Buyer name',
220
+ mandatory: true,
221
+ validate: (model) => {
222
+ if (!model.buyer?.name) {
223
+ return {
224
+ ruleId: 'BT-44',
225
+ severity: 'error',
226
+ message: 'Buyer name is mandatory',
227
+ field: 'buyer.name',
228
+ btReference: 'BT-44',
229
+ source: 'SEMANTIC'
230
+ };
231
+ }
232
+ return null;
233
+ }
234
+ },
235
+ {
236
+ btId: 'BT-55',
237
+ description: 'Buyer country code',
238
+ mandatory: true,
239
+ validate: (model) => {
240
+ if (!model.buyer?.postalAddress?.countryCode) {
241
+ return {
242
+ ruleId: 'BT-55',
243
+ severity: 'error',
244
+ message: 'Buyer country code is mandatory',
245
+ field: 'buyer.postalAddress.countryCode',
246
+ btReference: 'BT-55',
247
+ source: 'SEMANTIC'
248
+ };
249
+ }
250
+ // Validate ISO 3166-1 alpha-2 country code
251
+ if (!/^[A-Z]{2}$/.test(model.buyer.postalAddress.countryCode)) {
252
+ return {
253
+ ruleId: 'BT-55',
254
+ severity: 'error',
255
+ message: 'Country code must be a valid ISO 3166-1 alpha-2 code',
256
+ field: 'buyer.postalAddress.countryCode',
257
+ value: model.buyer.postalAddress.countryCode,
258
+ btReference: 'BT-55',
259
+ source: 'SEMANTIC'
260
+ };
261
+ }
262
+ return null;
263
+ }
264
+ },
265
+
266
+ // Payment means
267
+ {
268
+ btId: 'BT-81',
269
+ description: 'Payment means type code',
270
+ mandatory: true,
271
+ validate: (model) => {
272
+ if (!model.paymentInstructions?.paymentMeansTypeCode) {
273
+ return {
274
+ ruleId: 'BT-81',
275
+ severity: 'error',
276
+ message: 'Payment means type code is mandatory',
277
+ field: 'paymentInstructions.paymentMeansTypeCode',
278
+ btReference: 'BT-81',
279
+ source: 'SEMANTIC'
280
+ };
281
+ }
282
+ return null;
283
+ }
284
+ },
285
+
286
+ // Document totals
287
+ {
288
+ btId: 'BT-106',
289
+ description: 'Sum of invoice line net amount',
290
+ mandatory: true,
291
+ validate: (model) => {
292
+ if (model.documentTotals?.lineExtensionAmount === undefined) {
293
+ return {
294
+ ruleId: 'BT-106',
295
+ severity: 'error',
296
+ message: 'Sum of invoice line net amount is mandatory',
297
+ field: 'documentTotals.lineExtensionAmount',
298
+ btReference: 'BT-106',
299
+ source: 'SEMANTIC'
300
+ };
301
+ }
302
+ return null;
303
+ }
304
+ },
305
+ {
306
+ btId: 'BT-109',
307
+ description: 'Invoice total amount without VAT',
308
+ mandatory: true,
309
+ validate: (model) => {
310
+ if (model.documentTotals?.taxExclusiveAmount === undefined) {
311
+ return {
312
+ ruleId: 'BT-109',
313
+ severity: 'error',
314
+ message: 'Invoice total amount without VAT is mandatory',
315
+ field: 'documentTotals.taxExclusiveAmount',
316
+ btReference: 'BT-109',
317
+ source: 'SEMANTIC'
318
+ };
319
+ }
320
+ return null;
321
+ }
322
+ },
323
+ {
324
+ btId: 'BT-112',
325
+ description: 'Invoice total amount with VAT',
326
+ mandatory: true,
327
+ validate: (model) => {
328
+ if (model.documentTotals?.taxInclusiveAmount === undefined) {
329
+ return {
330
+ ruleId: 'BT-112',
331
+ severity: 'error',
332
+ message: 'Invoice total amount with VAT is mandatory',
333
+ field: 'documentTotals.taxInclusiveAmount',
334
+ btReference: 'BT-112',
335
+ source: 'SEMANTIC'
336
+ };
337
+ }
338
+ return null;
339
+ }
340
+ },
341
+ {
342
+ btId: 'BT-115',
343
+ description: 'Amount due for payment',
344
+ mandatory: true,
345
+ validate: (model) => {
346
+ if (model.documentTotals?.payableAmount === undefined) {
347
+ return {
348
+ ruleId: 'BT-115',
349
+ severity: 'error',
350
+ message: 'Amount due for payment is mandatory',
351
+ field: 'documentTotals.payableAmount',
352
+ btReference: 'BT-115',
353
+ source: 'SEMANTIC'
354
+ };
355
+ }
356
+ return null;
357
+ }
358
+ }
359
+ ];
360
+ }
361
+
362
+ /**
363
+ * Validate Business Groups
364
+ */
365
+ private validateBusinessGroups(model: EN16931SemanticModel): ValidationResult[] {
366
+ const results: ValidationResult[] = [];
367
+
368
+ // BG-4: Seller
369
+ if (!model.seller) {
370
+ results.push({
371
+ ruleId: 'BG-4',
372
+ severity: 'error',
373
+ message: 'Seller information is mandatory',
374
+ field: 'seller',
375
+ bgReference: 'BG-4',
376
+ source: 'SEMANTIC'
377
+ });
378
+ }
379
+
380
+ // BG-5: Seller postal address
381
+ if (!model.seller?.postalAddress) {
382
+ results.push({
383
+ ruleId: 'BG-5',
384
+ severity: 'error',
385
+ message: 'Seller postal address is mandatory',
386
+ field: 'seller.postalAddress',
387
+ bgReference: 'BG-5',
388
+ source: 'SEMANTIC'
389
+ });
390
+ }
391
+
392
+ // BG-7: Buyer
393
+ if (!model.buyer) {
394
+ results.push({
395
+ ruleId: 'BG-7',
396
+ severity: 'error',
397
+ message: 'Buyer information is mandatory',
398
+ field: 'buyer',
399
+ bgReference: 'BG-7',
400
+ source: 'SEMANTIC'
401
+ });
402
+ }
403
+
404
+ // BG-8: Buyer postal address
405
+ if (!model.buyer?.postalAddress) {
406
+ results.push({
407
+ ruleId: 'BG-8',
408
+ severity: 'error',
409
+ message: 'Buyer postal address is mandatory',
410
+ field: 'buyer.postalAddress',
411
+ bgReference: 'BG-8',
412
+ source: 'SEMANTIC'
413
+ });
414
+ }
415
+
416
+ // BG-16: Payment instructions
417
+ if (!model.paymentInstructions) {
418
+ results.push({
419
+ ruleId: 'BG-16',
420
+ severity: 'error',
421
+ message: 'Payment instructions are mandatory',
422
+ field: 'paymentInstructions',
423
+ bgReference: 'BG-16',
424
+ source: 'SEMANTIC'
425
+ });
426
+ }
427
+
428
+ // BG-22: Document totals
429
+ if (!model.documentTotals) {
430
+ results.push({
431
+ ruleId: 'BG-22',
432
+ severity: 'error',
433
+ message: 'Document totals are mandatory',
434
+ field: 'documentTotals',
435
+ bgReference: 'BG-22',
436
+ source: 'SEMANTIC'
437
+ });
438
+ }
439
+
440
+ // BG-25: Invoice lines
441
+ if (!model.invoiceLines || model.invoiceLines.length === 0) {
442
+ results.push({
443
+ ruleId: 'BG-25',
444
+ severity: 'error',
445
+ message: 'At least one invoice line is mandatory',
446
+ field: 'invoiceLines',
447
+ bgReference: 'BG-25',
448
+ source: 'SEMANTIC'
449
+ });
450
+ }
451
+
452
+ // Validate each invoice line
453
+ model.invoiceLines?.forEach((line, index) => {
454
+ // BT-126: Line identifier
455
+ if (!line.identifier) {
456
+ results.push({
457
+ ruleId: 'BT-126',
458
+ severity: 'error',
459
+ message: `Invoice line ${index + 1}: Identifier is mandatory`,
460
+ field: `invoiceLines[${index}].identifier`,
461
+ btReference: 'BT-126',
462
+ source: 'SEMANTIC'
463
+ });
464
+ }
465
+
466
+ // BT-129: Invoiced quantity
467
+ if (line.invoicedQuantity === undefined) {
468
+ results.push({
469
+ ruleId: 'BT-129',
470
+ severity: 'error',
471
+ message: `Invoice line ${index + 1}: Invoiced quantity is mandatory`,
472
+ field: `invoiceLines[${index}].invoicedQuantity`,
473
+ btReference: 'BT-129',
474
+ source: 'SEMANTIC'
475
+ });
476
+ }
477
+
478
+ // BT-131: Line net amount
479
+ if (line.lineExtensionAmount === undefined) {
480
+ results.push({
481
+ ruleId: 'BT-131',
482
+ severity: 'error',
483
+ message: `Invoice line ${index + 1}: Line net amount is mandatory`,
484
+ field: `invoiceLines[${index}].lineExtensionAmount`,
485
+ btReference: 'BT-131',
486
+ source: 'SEMANTIC'
487
+ });
488
+ }
489
+
490
+ // BT-153: Item name
491
+ if (!line.itemInformation?.name) {
492
+ results.push({
493
+ ruleId: 'BT-153',
494
+ severity: 'error',
495
+ message: `Invoice line ${index + 1}: Item name is mandatory`,
496
+ field: `invoiceLines[${index}].itemInformation.name`,
497
+ btReference: 'BT-153',
498
+ source: 'SEMANTIC'
499
+ });
500
+ }
501
+ });
502
+
503
+ return results;
504
+ }
505
+
506
+ /**
507
+ * Validate cardinality constraints
508
+ */
509
+ private validateCardinality(model: EN16931SemanticModel): ValidationResult[] {
510
+ const results: ValidationResult[] = [];
511
+
512
+ // Check for duplicate invoice lines
513
+ const lineIds = model.invoiceLines?.map(l => l.identifier) || [];
514
+ const uniqueIds = new Set(lineIds);
515
+ if (lineIds.length !== uniqueIds.size) {
516
+ results.push({
517
+ ruleId: 'CARD-01',
518
+ severity: 'error',
519
+ message: 'Invoice line identifiers must be unique',
520
+ field: 'invoiceLines',
521
+ source: 'SEMANTIC'
522
+ });
523
+ }
524
+
525
+ // Check VAT breakdown cardinality
526
+ if (model.vatBreakdown) {
527
+ const vatCategories = model.vatBreakdown.map(v => v.vatCategoryCode);
528
+ const uniqueCategories = new Set(vatCategories);
529
+ if (vatCategories.length !== uniqueCategories.size) {
530
+ results.push({
531
+ ruleId: 'CARD-02',
532
+ severity: 'error',
533
+ message: 'Each VAT category code must appear only once in VAT breakdown',
534
+ field: 'vatBreakdown',
535
+ source: 'SEMANTIC'
536
+ });
537
+ }
538
+ }
539
+
540
+ return results;
541
+ }
542
+
543
+ /**
544
+ * Validate conditional rules
545
+ */
546
+ private validateConditionalRules(model: EN16931SemanticModel): ValidationResult[] {
547
+ const results: ValidationResult[] = [];
548
+
549
+ // If VAT accounting currency code is present, VAT amount in accounting currency must be present
550
+ if (model.documentInformation.currencyCode !== model.documentInformation.currencyCode) {
551
+ if (!model.documentTotals?.taxInclusiveAmount) {
552
+ results.push({
553
+ ruleId: 'COND-01',
554
+ severity: 'error',
555
+ message: 'When VAT accounting currency differs from invoice currency, VAT amount in accounting currency is mandatory',
556
+ field: 'documentTotals.taxInclusiveAmount',
557
+ source: 'SEMANTIC'
558
+ });
559
+ }
560
+ }
561
+
562
+ // If credit note, there should be a preceding invoice reference
563
+ if (model.documentInformation.typeCode === '381') {
564
+ if (!model.references?.precedingInvoices || model.references.precedingInvoices.length === 0) {
565
+ results.push({
566
+ ruleId: 'COND-02',
567
+ severity: 'warning',
568
+ message: 'Credit notes should reference the original invoice',
569
+ field: 'references.precedingInvoices',
570
+ source: 'SEMANTIC'
571
+ });
572
+ }
573
+ }
574
+
575
+ // If tax representative is present, certain fields are mandatory
576
+ if (model.taxRepresentative) {
577
+ if (!model.taxRepresentative.vatIdentifier) {
578
+ results.push({
579
+ ruleId: 'COND-03',
580
+ severity: 'error',
581
+ message: 'Tax representative VAT identifier is mandatory when tax representative is present',
582
+ field: 'taxRepresentative.vatIdentifier',
583
+ source: 'SEMANTIC'
584
+ });
585
+ }
586
+ }
587
+
588
+ // VAT exemption requires exemption reason
589
+ if (model.vatBreakdown) {
590
+ for (const vat of model.vatBreakdown) {
591
+ if (vat.vatCategoryCode === 'E' && !vat.vatExemptionReasonText && !vat.vatExemptionReasonCode) {
592
+ results.push({
593
+ ruleId: 'COND-04',
594
+ severity: 'error',
595
+ message: 'VAT exemption requires exemption reason text or code',
596
+ field: 'vatBreakdown.vatExemptionReasonText',
597
+ source: 'SEMANTIC'
598
+ });
599
+ }
600
+ }
601
+ }
602
+
603
+ return results;
604
+ }
605
+
606
+ /**
607
+ * Get semantic model from invoice
608
+ */
609
+ public getSemanticModel(invoice: EInvoice): EN16931SemanticModel {
610
+ return this.adapter.toSemanticModel(invoice);
611
+ }
612
+
613
+ /**
614
+ * Create invoice from semantic model
615
+ */
616
+ public createInvoice(model: EN16931SemanticModel): EInvoice {
617
+ return this.adapter.fromSemanticModel(model);
618
+ }
619
+
620
+ /**
621
+ * Get BT/BG mapping for an invoice
622
+ */
623
+ public getBusinessTermMapping(invoice: EInvoice): Map<string, any> {
624
+ const model = this.adapter.toSemanticModel(invoice);
625
+ const mapping = new Map<string, any>();
626
+
627
+ // Map all business terms
628
+ mapping.set('BT-1', model.documentInformation.invoiceNumber);
629
+ mapping.set('BT-2', model.documentInformation.issueDate);
630
+ mapping.set('BT-3', model.documentInformation.typeCode);
631
+ mapping.set('BT-5', model.documentInformation.currencyCode);
632
+ mapping.set('BT-10', model.references?.buyerReference);
633
+ mapping.set('BT-27', model.seller?.name);
634
+ mapping.set('BT-40', model.seller?.postalAddress?.countryCode);
635
+ mapping.set('BT-44', model.buyer?.name);
636
+ mapping.set('BT-55', model.buyer?.postalAddress?.countryCode);
637
+ mapping.set('BT-81', model.paymentInstructions?.paymentMeansTypeCode);
638
+ mapping.set('BT-106', model.documentTotals?.lineExtensionAmount);
639
+ mapping.set('BT-109', model.documentTotals?.taxExclusiveAmount);
640
+ mapping.set('BT-112', model.documentTotals?.taxInclusiveAmount);
641
+ mapping.set('BT-115', model.documentTotals?.payableAmount);
642
+
643
+ // Map business groups
644
+ mapping.set('BG-4', model.seller);
645
+ mapping.set('BG-5', model.seller?.postalAddress);
646
+ mapping.set('BG-7', model.buyer);
647
+ mapping.set('BG-8', model.buyer?.postalAddress);
648
+ mapping.set('BG-16', model.paymentInstructions);
649
+ mapping.set('BG-22', model.documentTotals);
650
+ mapping.set('BG-25', model.invoiceLines);
651
+
652
+ return mapping;
653
+ }
654
+ }