@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,589 @@
1
+ /**
2
+ * PEPPOL BIS 3.0 validator for compliance with PEPPOL e-invoice specifications
3
+ * Implements PEPPOL-specific validation rules on top of EN16931
4
+ */
5
+
6
+ import type { ValidationResult } from './validation.types.js';
7
+ import type { EInvoice } from '../../einvoice.js';
8
+
9
+ /**
10
+ * PEPPOL BIS 3.0 Validator
11
+ * Implements PEPPOL-specific validation rules and constraints
12
+ */
13
+ export class PeppolValidator {
14
+ private static instance: PeppolValidator;
15
+
16
+ /**
17
+ * Singleton pattern for validator instance
18
+ */
19
+ public static create(): PeppolValidator {
20
+ if (!PeppolValidator.instance) {
21
+ PeppolValidator.instance = new PeppolValidator();
22
+ }
23
+ return PeppolValidator.instance;
24
+ }
25
+
26
+ /**
27
+ * Main validation entry point for PEPPOL
28
+ */
29
+ public validatePeppol(invoice: EInvoice): ValidationResult[] {
30
+ const results: ValidationResult[] = [];
31
+
32
+ // Check if this is a PEPPOL invoice
33
+ if (!this.isPeppolInvoice(invoice)) {
34
+ return results; // Not a PEPPOL invoice, skip validation
35
+ }
36
+
37
+ // Run all PEPPOL validations
38
+ results.push(...this.validateEndpointId(invoice));
39
+ results.push(...this.validateDocumentTypeId(invoice));
40
+ results.push(...this.validateProcessId(invoice));
41
+ results.push(...this.validatePartyIdentification(invoice));
42
+ results.push(...this.validatePeppolBusinessRules(invoice));
43
+ results.push(...this.validateSchemeIds(invoice));
44
+ results.push(...this.validateTransportProtocol(invoice));
45
+
46
+ return results;
47
+ }
48
+
49
+ /**
50
+ * Check if invoice is PEPPOL
51
+ */
52
+ private isPeppolInvoice(invoice: EInvoice): boolean {
53
+ const profileId = invoice.metadata?.profileId || '';
54
+ const customizationId = invoice.metadata?.customizationId || '';
55
+
56
+ const peppolProfiles = [
57
+ 'urn:fdc:peppol.eu:2017:poacc:billing:3.0',
58
+ 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
59
+ 'peppol-bis-3',
60
+ 'peppol'
61
+ ];
62
+
63
+ return peppolProfiles.some(profile =>
64
+ profileId.toLowerCase().includes(profile.toLowerCase()) ||
65
+ customizationId.toLowerCase().includes(profile.toLowerCase())
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Validate Endpoint ID format (0088:xxxxxxxxx or other schemes)
71
+ * PEPPOL-T001, PEPPOL-T002
72
+ */
73
+ private validateEndpointId(invoice: EInvoice): ValidationResult[] {
74
+ const results: ValidationResult[] = [];
75
+
76
+ // Check seller endpoint ID
77
+ const sellerEndpointId = invoice.metadata?.extensions?.sellerEndpointId ||
78
+ invoice.metadata?.extensions?.peppolSellerEndpoint;
79
+
80
+ if (sellerEndpointId) {
81
+ if (!this.isValidEndpointId(sellerEndpointId)) {
82
+ results.push({
83
+ ruleId: 'PEPPOL-T001',
84
+ severity: 'error',
85
+ message: 'Invalid seller endpoint ID format. Expected format: scheme:identifier (e.g., 0088:1234567890128)',
86
+ field: 'metadata.extensions.sellerEndpointId',
87
+ value: sellerEndpointId,
88
+ source: 'PEPPOL'
89
+ });
90
+ }
91
+ } else if (this.isPeppolB2G(invoice)) {
92
+ // Endpoint ID is mandatory for B2G
93
+ results.push({
94
+ ruleId: 'PEPPOL-T001',
95
+ severity: 'error',
96
+ message: 'Seller endpoint ID is mandatory for PEPPOL B2G invoices',
97
+ field: 'metadata.extensions.sellerEndpointId',
98
+ source: 'PEPPOL'
99
+ });
100
+ }
101
+
102
+ // Check buyer endpoint ID
103
+ const buyerEndpointId = invoice.metadata?.extensions?.buyerEndpointId ||
104
+ invoice.metadata?.extensions?.peppolBuyerEndpoint;
105
+
106
+ if (buyerEndpointId) {
107
+ if (!this.isValidEndpointId(buyerEndpointId)) {
108
+ results.push({
109
+ ruleId: 'PEPPOL-T002',
110
+ severity: 'error',
111
+ message: 'Invalid buyer endpoint ID format. Expected format: scheme:identifier (e.g., 0088:1234567890128)',
112
+ field: 'metadata.extensions.buyerEndpointId',
113
+ value: buyerEndpointId,
114
+ source: 'PEPPOL'
115
+ });
116
+ }
117
+ } else if (this.isPeppolB2G(invoice)) {
118
+ // Endpoint ID is mandatory for B2G
119
+ results.push({
120
+ ruleId: 'PEPPOL-T002',
121
+ severity: 'error',
122
+ message: 'Buyer endpoint ID is mandatory for PEPPOL B2G invoices',
123
+ field: 'metadata.extensions.buyerEndpointId',
124
+ source: 'PEPPOL'
125
+ });
126
+ }
127
+
128
+ return results;
129
+ }
130
+
131
+ /**
132
+ * Validate endpoint ID format
133
+ */
134
+ private isValidEndpointId(endpointId: string): boolean {
135
+ // PEPPOL endpoint ID format: scheme:identifier
136
+ // Common schemes: 0088 (GLN), 0192 (Norwegian org), 9906 (IT VAT), etc.
137
+ const endpointPattern = /^[0-9]{4}:[A-Za-z0-9\-._]+$/;
138
+
139
+ // Special validation for GLN (0088)
140
+ if (endpointId.startsWith('0088:')) {
141
+ const gln = endpointId.substring(5);
142
+ // GLN should be 13 digits
143
+ if (!/^\d{13}$/.test(gln)) {
144
+ return false;
145
+ }
146
+ // Validate GLN check digit
147
+ return this.validateGLNCheckDigit(gln);
148
+ }
149
+
150
+ return endpointPattern.test(endpointId);
151
+ }
152
+
153
+ /**
154
+ * Validate GLN check digit using modulo 10
155
+ */
156
+ private validateGLNCheckDigit(gln: string): boolean {
157
+ if (gln.length !== 13) return false;
158
+
159
+ let sum = 0;
160
+ for (let i = 0; i < 12; i++) {
161
+ const digit = parseInt(gln[i], 10);
162
+ sum += digit * (i % 2 === 0 ? 1 : 3);
163
+ }
164
+
165
+ const checkDigit = (10 - (sum % 10)) % 10;
166
+ return checkDigit === parseInt(gln[12], 10);
167
+ }
168
+
169
+ /**
170
+ * Validate Document Type ID
171
+ * PEPPOL-T003
172
+ */
173
+ private validateDocumentTypeId(invoice: EInvoice): ValidationResult[] {
174
+ const results: ValidationResult[] = [];
175
+
176
+ const documentTypeId = invoice.metadata?.extensions?.documentTypeId ||
177
+ invoice.metadata?.extensions?.peppolDocumentType;
178
+
179
+ if (!documentTypeId && this.isPeppolB2G(invoice)) {
180
+ results.push({
181
+ ruleId: 'PEPPOL-T003',
182
+ severity: 'error',
183
+ message: 'Document type ID is mandatory for PEPPOL invoices',
184
+ field: 'metadata.extensions.documentTypeId',
185
+ source: 'PEPPOL'
186
+ });
187
+ } else if (documentTypeId) {
188
+ // Validate against known PEPPOL document types
189
+ const validDocumentTypes = [
190
+ 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1',
191
+ 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1',
192
+ // Add more valid document types as needed
193
+ ];
194
+
195
+ if (!validDocumentTypes.some(type => documentTypeId.includes(type))) {
196
+ results.push({
197
+ ruleId: 'PEPPOL-T003',
198
+ severity: 'warning',
199
+ message: 'Document type ID may not be a valid PEPPOL document type',
200
+ field: 'metadata.extensions.documentTypeId',
201
+ value: documentTypeId,
202
+ source: 'PEPPOL'
203
+ });
204
+ }
205
+ }
206
+
207
+ return results;
208
+ }
209
+
210
+ /**
211
+ * Validate Process ID
212
+ * PEPPOL-T004
213
+ */
214
+ private validateProcessId(invoice: EInvoice): ValidationResult[] {
215
+ const results: ValidationResult[] = [];
216
+
217
+ const processId = invoice.metadata?.extensions?.processId ||
218
+ invoice.metadata?.extensions?.peppolProcessId;
219
+
220
+ if (!processId && this.isPeppolB2G(invoice)) {
221
+ results.push({
222
+ ruleId: 'PEPPOL-T004',
223
+ severity: 'error',
224
+ message: 'Process ID is mandatory for PEPPOL invoices',
225
+ field: 'metadata.extensions.processId',
226
+ source: 'PEPPOL'
227
+ });
228
+ } else if (processId) {
229
+ // Validate against known PEPPOL processes
230
+ const validProcessIds = [
231
+ 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
232
+ 'urn:fdc:peppol.eu:2017:poacc:billing:3.0',
233
+ // Legacy process IDs
234
+ 'urn:www.cenbii.eu:profile:bii05:ver2.0',
235
+ 'urn:www.cenbii.eu:profile:bii04:ver2.0'
236
+ ];
237
+
238
+ if (!validProcessIds.includes(processId)) {
239
+ results.push({
240
+ ruleId: 'PEPPOL-T004',
241
+ severity: 'warning',
242
+ message: 'Process ID may not be a valid PEPPOL process',
243
+ field: 'metadata.extensions.processId',
244
+ value: processId,
245
+ source: 'PEPPOL'
246
+ });
247
+ }
248
+ }
249
+
250
+ return results;
251
+ }
252
+
253
+ /**
254
+ * Validate Party Identification Schemes
255
+ * PEPPOL-T005, PEPPOL-T006
256
+ */
257
+ private validatePartyIdentification(invoice: EInvoice): ValidationResult[] {
258
+ const results: ValidationResult[] = [];
259
+
260
+ // Validate seller party identification
261
+ if (invoice.from?.type === 'company') {
262
+ const company = invoice.from as any;
263
+ const partyId = company.registrationDetails?.peppolPartyId ||
264
+ company.registrationDetails?.partyIdentification;
265
+
266
+ if (partyId && partyId.schemeId) {
267
+ if (!this.isValidSchemeId(partyId.schemeId)) {
268
+ results.push({
269
+ ruleId: 'PEPPOL-T005',
270
+ severity: 'warning',
271
+ message: 'Seller party identification scheme may not be valid',
272
+ field: 'from.registrationDetails.partyIdentification.schemeId',
273
+ value: partyId.schemeId,
274
+ source: 'PEPPOL'
275
+ });
276
+ }
277
+ }
278
+ }
279
+
280
+ // Validate buyer party identification
281
+ const buyerPartyId = invoice.metadata?.extensions?.buyerPartyId;
282
+ if (buyerPartyId && buyerPartyId.schemeId) {
283
+ if (!this.isValidSchemeId(buyerPartyId.schemeId)) {
284
+ results.push({
285
+ ruleId: 'PEPPOL-T006',
286
+ severity: 'warning',
287
+ message: 'Buyer party identification scheme may not be valid',
288
+ field: 'metadata.extensions.buyerPartyId.schemeId',
289
+ value: buyerPartyId.schemeId,
290
+ source: 'PEPPOL'
291
+ });
292
+ }
293
+ }
294
+
295
+ return results;
296
+ }
297
+
298
+ /**
299
+ * Validate scheme IDs against PEPPOL code list
300
+ */
301
+ private isValidSchemeId(schemeId: string): boolean {
302
+ // PEPPOL Party Identifier Scheme (subset of ISO 6523 ICD list)
303
+ const validSchemes = [
304
+ '0002', // System Information et Repertoire des Entreprise et des Etablissements (SIRENE)
305
+ '0007', // Organisationsnummer (Swedish legal entities)
306
+ '0009', // SIRET
307
+ '0037', // LY-tunnus (Finnish business ID)
308
+ '0060', // DUNS number
309
+ '0088', // EAN Location Code (GLN)
310
+ '0096', // VIOC (Danish CVR)
311
+ '0097', // Danish Ministry of the Interior and Health
312
+ '0106', // Netherlands Chamber of Commerce
313
+ '0130', // Direktoratet for forvaltning og IKT (DIFI)
314
+ '0135', // IT:SIA
315
+ '0142', // IT:SECETI
316
+ '0184', // Danish CVR
317
+ '0190', // Dutch Originator's Identification Number
318
+ '0191', // Centre of Registers and Information Systems of the Ministry of Justice (Estonia)
319
+ '0192', // Norwegian Legal Entity
320
+ '0193', // UBL.BE party identifier
321
+ '0195', // Singapore UEN
322
+ '0196', // Kennitala (Iceland)
323
+ '0198', // ERSTORG
324
+ '0199', // Legal Entity Identifier (LEI)
325
+ '0200', // Legal entity code (Lithuania)
326
+ '0201', // CODICE UNIVOCO UNITÀ ORGANIZZATIVA
327
+ '0204', // German Leitweg-ID
328
+ '0208', // Belgian enterprise number
329
+ '0209', // GS1 identification keys
330
+ '0210', // CODICE FISCALE
331
+ '0211', // PARTITA IVA
332
+ '0212', // Finnish Organization Number
333
+ '0213', // Finnish VAT number
334
+ '9901', // Danish CVR
335
+ '9902', // Danish SE
336
+ '9904', // German VAT number
337
+ '9905', // German Leitweg ID
338
+ '9906', // IT:VAT
339
+ '9907', // IT:CF
340
+ '9910', // HU:VAT
341
+ '9914', // AT:VAT
342
+ '9915', // AT:GOV
343
+ '9917', // Netherlands OIN
344
+ '9918', // IS:KT
345
+ '9919', // IS company code
346
+ '9920', // ES:VAT
347
+ '9922', // AD:VAT
348
+ '9923', // AL:VAT
349
+ '9924', // BA:VAT
350
+ '9925', // BE:VAT
351
+ '9926', // BG:VAT
352
+ '9927', // CH:VAT
353
+ '9928', // CY:VAT
354
+ '9929', // CZ:VAT
355
+ '9930', // DE:VAT
356
+ '9931', // EE:VAT
357
+ '9932', // GB:VAT
358
+ '9933', // GR:VAT
359
+ '9934', // HR:VAT
360
+ '9935', // IE:VAT
361
+ '9936', // LI:VAT
362
+ '9937', // LT:VAT
363
+ '9938', // LU:VAT
364
+ '9939', // LV:VAT
365
+ '9940', // MC:VAT
366
+ '9941', // ME:VAT
367
+ '9942', // MK:VAT
368
+ '9943', // MT:VAT
369
+ '9944', // NL:VAT
370
+ '9945', // PL:VAT
371
+ '9946', // PT:VAT
372
+ '9947', // RO:VAT
373
+ '9948', // RS:VAT
374
+ '9949', // SI:VAT
375
+ '9950', // SK:VAT
376
+ '9951', // SM:VAT
377
+ '9952', // TR:VAT
378
+ '9953', // VA:VAT
379
+ '9955', // SE:VAT
380
+ '9956', // BE:CBE
381
+ '9957', // FR:VAT
382
+ '9958', // German Leitweg ID
383
+ ];
384
+
385
+ return validSchemes.includes(schemeId);
386
+ }
387
+
388
+ /**
389
+ * Validate PEPPOL-specific business rules
390
+ */
391
+ private validatePeppolBusinessRules(invoice: EInvoice): ValidationResult[] {
392
+ const results: ValidationResult[] = [];
393
+
394
+ // PEPPOL-B-01: Invoice must have a buyer reference or purchase order reference
395
+ const purchaseOrderRef = invoice.metadata?.extensions?.purchaseOrderReference;
396
+ if (!invoice.metadata?.buyerReference && !purchaseOrderRef) {
397
+ results.push({
398
+ ruleId: 'PEPPOL-B-01',
399
+ severity: 'error',
400
+ message: 'Invoice must have either a buyer reference (BT-10) or purchase order reference (BT-13)',
401
+ field: 'metadata.buyerReference',
402
+ source: 'PEPPOL'
403
+ });
404
+ }
405
+
406
+ // PEPPOL-B-02: Seller electronic address is mandatory
407
+ const sellerEmail = invoice.from?.type === 'company' ?
408
+ (invoice.from as any).contact?.email :
409
+ (invoice.from as any)?.email;
410
+
411
+ if (!sellerEmail) {
412
+ results.push({
413
+ ruleId: 'PEPPOL-B-02',
414
+ severity: 'warning',
415
+ message: 'Seller electronic address (email) is recommended for PEPPOL invoices',
416
+ field: 'from.contact.email',
417
+ source: 'PEPPOL'
418
+ });
419
+ }
420
+
421
+ // PEPPOL-B-03: Item standard identifier
422
+ if (invoice.items && invoice.items.length > 0) {
423
+ invoice.items.forEach((item, index) => {
424
+ const itemId = (item as any).standardItemIdentification;
425
+ if (!itemId) {
426
+ results.push({
427
+ ruleId: 'PEPPOL-B-03',
428
+ severity: 'info',
429
+ message: `Item ${index + 1} should have a standard item identification (GTIN, EAN, etc.)`,
430
+ field: `items[${index}].standardItemIdentification`,
431
+ source: 'PEPPOL'
432
+ });
433
+ } else if (itemId.schemeId === '0160' && !this.isValidGTIN(itemId.id)) {
434
+ // Validate GTIN if scheme is 0160
435
+ results.push({
436
+ ruleId: 'PEPPOL-B-03',
437
+ severity: 'error',
438
+ message: `Item ${index + 1} has invalid GTIN`,
439
+ field: `items[${index}].standardItemIdentification.id`,
440
+ value: itemId.id,
441
+ source: 'PEPPOL'
442
+ });
443
+ }
444
+ });
445
+ }
446
+
447
+ // PEPPOL-B-04: Payment means code must be from UNCL4461
448
+ const paymentMeansCode = invoice.metadata?.extensions?.paymentMeans?.paymentMeansCode;
449
+ if (paymentMeansCode) {
450
+ const validPaymentMeans = [
451
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10',
452
+ '11', '12', '13', '14', '15', '16', '17', '18', '19', '20',
453
+ '21', '22', '23', '24', '25', '26', '27', '28', '29', '30',
454
+ '31', '32', '33', '34', '35', '36', '37', '38', '39', '40',
455
+ '41', '42', '43', '44', '45', '46', '47', '48', '49', '50',
456
+ '51', '52', '53', '54', '55', '56', '57', '58', '59', '60',
457
+ '61', '62', '63', '64', '65', '66', '67', '68', '70', '74',
458
+ '75', '76', '77', '78', '91', '92', '93', '94', '95', '96', '97', 'ZZZ'
459
+ ];
460
+
461
+ if (!validPaymentMeans.includes(paymentMeansCode)) {
462
+ results.push({
463
+ ruleId: 'PEPPOL-B-04',
464
+ severity: 'error',
465
+ message: 'Payment means code must be from UNCL4461 code list',
466
+ field: 'metadata.extensions.paymentMeans.paymentMeansCode',
467
+ value: paymentMeansCode,
468
+ source: 'PEPPOL'
469
+ });
470
+ }
471
+ }
472
+
473
+ return results;
474
+ }
475
+
476
+ /**
477
+ * Validate GTIN (Global Trade Item Number)
478
+ */
479
+ private isValidGTIN(gtin: string): boolean {
480
+ // GTIN can be 8, 12, 13, or 14 digits
481
+ if (!/^(\d{8}|\d{12}|\d{13}|\d{14})$/.test(gtin)) {
482
+ return false;
483
+ }
484
+
485
+ // Validate check digit
486
+ const digits = gtin.split('').map(d => parseInt(d, 10));
487
+ const checkDigit = digits[digits.length - 1];
488
+
489
+ let sum = 0;
490
+ for (let i = digits.length - 2; i >= 0; i--) {
491
+ const multiplier = ((digits.length - 2 - i) % 2 === 0) ? 3 : 1;
492
+ sum += digits[i] * multiplier;
493
+ }
494
+
495
+ const calculatedCheck = (10 - (sum % 10)) % 10;
496
+ return calculatedCheck === checkDigit;
497
+ }
498
+
499
+ /**
500
+ * Validate scheme IDs used in the invoice
501
+ */
502
+ private validateSchemeIds(invoice: EInvoice): ValidationResult[] {
503
+ const results: ValidationResult[] = [];
504
+
505
+ // Check tax scheme ID
506
+ const taxSchemeId = invoice.metadata?.extensions?.taxDetails?.[0]?.taxScheme?.id;
507
+ if (taxSchemeId && taxSchemeId !== 'VAT') {
508
+ results.push({
509
+ ruleId: 'PEPPOL-S-01',
510
+ severity: 'warning',
511
+ message: 'Tax scheme ID should be "VAT" for PEPPOL invoices',
512
+ field: 'metadata.extensions.taxDetails[0].taxScheme.id',
513
+ value: taxSchemeId,
514
+ source: 'PEPPOL'
515
+ });
516
+ }
517
+
518
+ // Check currency code is from ISO 4217
519
+ if (invoice.currency) {
520
+ // This is already validated by CodeListValidator, but we can add PEPPOL-specific check
521
+ if (!['EUR', 'USD', 'GBP', 'SEK', 'NOK', 'DKK', 'CHF', 'PLN', 'CZK', 'HUF'].includes(invoice.currency)) {
522
+ results.push({
523
+ ruleId: 'PEPPOL-S-02',
524
+ severity: 'info',
525
+ message: `Currency ${invoice.currency} is uncommon in PEPPOL network`,
526
+ field: 'currency',
527
+ value: invoice.currency,
528
+ source: 'PEPPOL'
529
+ });
530
+ }
531
+ }
532
+
533
+ return results;
534
+ }
535
+
536
+ /**
537
+ * Validate transport protocol requirements
538
+ */
539
+ private validateTransportProtocol(invoice: EInvoice): ValidationResult[] {
540
+ const results: ValidationResult[] = [];
541
+
542
+ // Check if transport protocol is specified
543
+ const transportProtocol = invoice.metadata?.extensions?.transportProtocol;
544
+ if (transportProtocol) {
545
+ const validProtocols = ['AS2', 'AS4'];
546
+ if (!validProtocols.includes(transportProtocol)) {
547
+ results.push({
548
+ ruleId: 'PEPPOL-P-01',
549
+ severity: 'warning',
550
+ message: 'Transport protocol should be AS2 or AS4 for PEPPOL',
551
+ field: 'metadata.extensions.transportProtocol',
552
+ value: transportProtocol,
553
+ source: 'PEPPOL'
554
+ });
555
+ }
556
+ }
557
+
558
+ // Check if SMP lookup is possible
559
+ const sellerEndpointId = invoice.metadata?.extensions?.sellerEndpointId;
560
+ if (sellerEndpointId && !invoice.metadata?.extensions?.smpRegistered) {
561
+ results.push({
562
+ ruleId: 'PEPPOL-P-02',
563
+ severity: 'info',
564
+ message: 'Seller endpoint should be registered in PEPPOL SMP for discovery',
565
+ field: 'metadata.extensions.smpRegistered',
566
+ source: 'PEPPOL'
567
+ });
568
+ }
569
+
570
+ return results;
571
+ }
572
+
573
+ /**
574
+ * Check if invoice is B2G (Business to Government)
575
+ */
576
+ private isPeppolB2G(invoice: EInvoice): boolean {
577
+ // Check if buyer has government indicators
578
+ const buyerSchemeId = invoice.metadata?.extensions?.buyerPartyId?.schemeId;
579
+ const buyerCategory = invoice.metadata?.extensions?.buyerCategory;
580
+
581
+ // Government scheme IDs often include specific codes
582
+ const governmentSchemes = ['0204', '9905', '0197', '0215'];
583
+
584
+ // Check various indicators for government entity
585
+ return buyerCategory === 'government' ||
586
+ (buyerSchemeId && governmentSchemes.includes(buyerSchemeId)) ||
587
+ invoice.metadata?.extensions?.isB2G === true;
588
+ }
589
+ }