@fin.cx/einvoice 8.3.1 → 10.0.0

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 (139) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/einvoice.d.ts +26 -0
  3. package/dist_ts/einvoice.js +61 -3
  4. package/dist_ts/formats/base/base.decoder.d.ts +18 -1
  5. package/dist_ts/formats/base/base.decoder.js +30 -17
  6. package/dist_ts/formats/cii/cii.decoder.d.ts +89 -6
  7. package/dist_ts/formats/cii/cii.decoder.js +125 -30
  8. package/dist_ts/formats/cii/cii.encoder.d.ts +71 -1
  9. package/dist_ts/formats/cii/cii.encoder.js +195 -12
  10. package/dist_ts/formats/cii/cii.types.d.ts +2 -5
  11. package/dist_ts/formats/cii/cii.types.js +7 -9
  12. package/dist_ts/formats/cii/cii.validator.d.ts +13 -6
  13. package/dist_ts/formats/cii/cii.validator.js +29 -30
  14. package/dist_ts/formats/cii/facturx/facturx.decoder.js +19 -3
  15. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +0 -6
  16. package/dist_ts/formats/cii/facturx/facturx.encoder.js +17 -26
  17. package/dist_ts/formats/cii/facturx/facturx.types.d.ts +0 -5
  18. package/dist_ts/formats/cii/facturx/facturx.types.js +1 -8
  19. package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +0 -5
  20. package/dist_ts/formats/cii/facturx/facturx.validator.js +5 -29
  21. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +19 -2
  22. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +0 -7
  23. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +15 -68
  24. package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +0 -5
  25. package/dist_ts/formats/cii/zugferd/zugferd.types.js +1 -8
  26. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +26 -0
  27. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +47 -1
  28. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +6 -2
  29. package/dist_ts/formats/semantic/semantic.adapter.js +29 -20
  30. package/dist_ts/formats/semantic/semantic.validator.js +17 -2
  31. package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +0 -4
  32. package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -42
  33. package/dist_ts/formats/ubl/generic/ubl.encoder.js +29 -26
  34. package/dist_ts/formats/ubl/ubl.decoder.d.ts +4 -0
  35. package/dist_ts/formats/ubl/ubl.decoder.js +9 -1
  36. package/dist_ts/formats/ubl/ubl.encoder.js +13 -5
  37. package/dist_ts/formats/ubl/ubl.validator.d.ts +13 -0
  38. package/dist_ts/formats/ubl/ubl.validator.js +28 -1
  39. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +14 -1
  40. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +0 -7
  41. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +30 -45
  42. package/dist_ts/formats/ubl/xrechnung.validator.d.ts +5 -4
  43. package/dist_ts/formats/ubl/xrechnung.validator.js +71 -68
  44. package/dist_ts/formats/utils/date.value.d.ts +11 -0
  45. package/dist_ts/formats/utils/date.value.js +33 -1
  46. package/dist_ts/formats/utils/document.totals.d.ts +1 -3
  47. package/dist_ts/formats/utils/document.totals.js +1 -2
  48. package/dist_ts/formats/utils/format.detector.js +16 -10
  49. package/dist_ts/formats/utils/paid.amount.d.ts +44 -0
  50. package/dist_ts/formats/utils/paid.amount.js +124 -0
  51. package/dist_ts/formats/utils/party.contact.d.ts +16 -0
  52. package/dist_ts/formats/utils/party.contact.js +16 -0
  53. package/dist_ts/formats/utils/party.identifier.d.ts +28 -0
  54. package/dist_ts/formats/utils/party.identifier.js +49 -0
  55. package/dist_ts/formats/utils/peppol.profile.d.ts +10 -0
  56. package/dist_ts/formats/utils/peppol.profile.js +12 -0
  57. package/dist_ts/formats/utils/preceding.invoice.d.ts +12 -2
  58. package/dist_ts/formats/utils/preceding.invoice.js +22 -3
  59. package/dist_ts/formats/utils/seller.identifier.d.ts +46 -0
  60. package/dist_ts/formats/utils/seller.identifier.js +78 -0
  61. package/dist_ts/formats/utils/stated.values.d.ts +80 -0
  62. package/dist_ts/formats/utils/stated.values.js +418 -0
  63. package/dist_ts/formats/utils/vat.category.d.ts +30 -2
  64. package/dist_ts/formats/utils/vat.category.js +36 -6
  65. package/dist_ts/formats/utils/vat.id.d.ts +18 -0
  66. package/dist_ts/formats/utils/vat.id.js +22 -0
  67. package/dist_ts/formats/validation/conformance.harness.js +6 -6
  68. package/dist_ts/formats/validation/en16931.business-rules.validator.js +4 -19
  69. package/dist_ts/formats/validation/facturx.validator.js +6 -6
  70. package/dist_ts/formats/validation/integrated.validator.js +4 -13
  71. package/dist_ts/formats/validation/peppol.validator.js +6 -13
  72. package/dist_ts/formats/validation/validation.types.d.ts +5 -0
  73. package/dist_ts/formats/validation/validation.types.js +6 -1
  74. package/dist_ts/formats/validation/vat-categories.validator.d.ts +21 -42
  75. package/dist_ts/formats/validation/vat-categories.validator.js +137 -431
  76. package/dist_ts/formats/validation/xrechnung.validator.d.ts +11 -58
  77. package/dist_ts/formats/validation/xrechnung.validator.js +58 -324
  78. package/dist_ts/index.d.ts +1 -0
  79. package/dist_ts/index.js +1 -1
  80. package/dist_ts/interfaces/common.d.ts +1 -0
  81. package/dist_ts/interfaces/en16931-metadata.d.ts +0 -5
  82. package/dist_ts/interfaces/stated.values.d.ts +93 -0
  83. package/dist_ts/interfaces/stated.values.js +2 -0
  84. package/package.json +2 -2
  85. package/readme.md +182 -6
  86. package/ts/00_commitinfo_data.ts +1 -1
  87. package/ts/einvoice.ts +70 -2
  88. package/ts/formats/base/base.decoder.ts +32 -29
  89. package/ts/formats/cii/cii.decoder.ts +159 -32
  90. package/ts/formats/cii/cii.encoder.ts +210 -14
  91. package/ts/formats/cii/cii.types.ts +7 -9
  92. package/ts/formats/cii/cii.validator.ts +30 -32
  93. package/ts/formats/cii/facturx/facturx.decoder.ts +21 -2
  94. package/ts/formats/cii/facturx/facturx.encoder.ts +19 -26
  95. package/ts/formats/cii/facturx/facturx.types.ts +0 -9
  96. package/ts/formats/cii/facturx/facturx.validator.ts +5 -43
  97. package/ts/formats/cii/zugferd/zugferd.decoder.ts +21 -1
  98. package/ts/formats/cii/zugferd/zugferd.encoder.ts +16 -73
  99. package/ts/formats/cii/zugferd/zugferd.types.ts +0 -9
  100. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +66 -0
  101. package/ts/formats/cii/zugferd/zugferd.validator.ts +5 -1
  102. package/ts/formats/semantic/semantic.adapter.ts +28 -19
  103. package/ts/formats/semantic/semantic.validator.ts +17 -1
  104. package/ts/formats/ubl/en16931.ubl.validator.ts +5 -64
  105. package/ts/formats/ubl/generic/ubl.encoder.ts +31 -25
  106. package/ts/formats/ubl/ubl.decoder.ts +12 -0
  107. package/ts/formats/ubl/ubl.encoder.ts +12 -4
  108. package/ts/formats/ubl/ubl.validator.ts +29 -0
  109. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +13 -0
  110. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +33 -45
  111. package/ts/formats/ubl/xrechnung.validator.ts +75 -127
  112. package/ts/formats/utils/date.value.ts +33 -0
  113. package/ts/formats/utils/document.totals.ts +1 -4
  114. package/ts/formats/utils/format.detector.ts +15 -9
  115. package/ts/formats/utils/paid.amount.ts +160 -0
  116. package/ts/formats/utils/party.contact.ts +30 -0
  117. package/ts/formats/utils/party.identifier.ts +61 -0
  118. package/ts/formats/utils/peppol.profile.ts +13 -0
  119. package/ts/formats/utils/preceding.invoice.ts +23 -2
  120. package/ts/formats/utils/seller.identifier.ts +102 -0
  121. package/ts/formats/utils/stated.values.ts +499 -0
  122. package/ts/formats/utils/vat.category.ts +47 -5
  123. package/ts/formats/utils/vat.id.ts +24 -0
  124. package/ts/formats/validation/conformance.harness.ts +5 -5
  125. package/ts/formats/validation/en16931.business-rules.validator.ts +3 -26
  126. package/ts/formats/validation/facturx.validator.ts +5 -5
  127. package/ts/formats/validation/integrated.validator.ts +3 -16
  128. package/ts/formats/validation/peppol.validator.ts +5 -16
  129. package/ts/formats/validation/validation.types.ts +7 -1
  130. package/ts/formats/validation/vat-categories.validator.ts +179 -761
  131. package/ts/formats/validation/xrechnung.validator.ts +61 -382
  132. package/ts/index.ts +9 -0
  133. package/ts/interfaces/common.ts +1 -0
  134. package/ts/interfaces/en16931-metadata.ts +3 -8
  135. package/ts/interfaces/stated.values.ts +94 -0
  136. package/ts/readme.md +1 -1
  137. package/dist_ts/formats/utils/eu.memberstates.d.ts +0 -11
  138. package/dist_ts/formats/utils/eu.memberstates.js +0 -16
  139. package/ts/formats/utils/eu.memberstates.ts +0 -16
@@ -44,8 +44,46 @@ export const getVatExemption = (category: TVatCategoryCode, language: string | u
44
44
  : undefined;
45
45
 
46
46
  /**
47
- * Refuses a reverse charge document that lacks what EN 16931 requires of one,
48
- * naming the rule; no value is put in place of a missing one.
47
+ * Whether a VAT rate is a number not greater than zero; a rate that is no
48
+ * number is refused as such (`findInvalidItemAmounts`), not as BR-S-05
49
+ * @param rate The rate
50
+ */
51
+ export const isRateNotAboveZero = (rate: unknown): boolean => typeof rate === 'number' && Number.isFinite(rate) && !(rate > 0);
52
+
53
+ /** A line written as standard rated (S) whose VAT rate is not greater than zero */
54
+ export interface IStandardRatedLineWithoutRate {
55
+ index: number;
56
+ vatPercentage: number;
57
+ }
58
+
59
+ /**
60
+ * The first line of a document that is not reverse charge whose VAT rate is not
61
+ * greater than zero. The encoders write every such line as standard rated (S),
62
+ * and EN 16931 BR-S-05 asks a standard rated line for a rate greater than zero;
63
+ * the category the line has (zero rated Z, exempt E, intra-community supply K,
64
+ * export G, not subject to VAT O) cannot be stated until an item carries a VAT
65
+ * category of its own.
66
+ * @param items The document's items
67
+ */
68
+ export const findStandardRatedLineWithoutRate = (
69
+ items: TAccountingDoc['items'] | undefined,
70
+ ): IStandardRatedLineWithoutRate | undefined => {
71
+ const index = (items ?? []).findIndex((item) => isRateNotAboveZero(item.vatPercentage));
72
+ return index < 0 ? undefined : { index, vatPercentage: items![index].vatPercentage };
73
+ };
74
+
75
+ /**
76
+ * The refusal or finding for such a line
77
+ * @param line The line
78
+ */
79
+ export const standardRatedLineWithoutRateMessage = (line: IStandardRatedLineWithoutRate): string =>
80
+ `In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Standard rated" the Invoiced item VAT rate (BT-152) shall be greater than zero; items[${line.index}].vatPercentage is ${String(line.vatPercentage)}. A line outside reverse charge is written as standard rated (S); its category, zero rated, exempt, intra-community supply or export (Z, E, K, G or O), cannot be stated until an item carries a VAT category of its own`;
81
+
82
+ /**
83
+ * Refuses a document whose lines cannot be written with a valid VAT category,
84
+ * naming the rule: a line outside reverse charge whose rate is not greater than
85
+ * zero (BR-S-05), and a reverse charge document that lacks what EN 16931
86
+ * requires of one; no value is put in place of a missing one.
49
87
  * - BR-AE-05: every line of a reverse charge document has the VAT rate 0; the
50
88
  * recipient owes the tax, and the invoice states none (§ 14a Abs. 5 Satz 2
51
89
  * UStG; a stated amount would be owed under § 14c Abs. 1 UStG).
@@ -66,12 +104,16 @@ export const getVatExemption = (category: TVatCategoryCode, language: string | u
66
104
  * @param targetFormat The format being written
67
105
  */
68
106
  export const assertVatCategoryWritable = (accountingDoc: TAccountingDoc, targetFormat: string): void => {
69
- if (!accountingDoc.reverseCharge) {
70
- return;
71
- }
72
107
  const refuse = (rule: string, message: string): never => {
73
108
  throw new EInvoiceFormatError(`${rule}: ${message}`, { targetFormat, unsupportedFeatures: [rule] });
74
109
  };
110
+ if (!accountingDoc.reverseCharge) {
111
+ const line = findStandardRatedLineWithoutRate(accountingDoc.items);
112
+ if (line) {
113
+ refuse('BR-S-05', standardRatedLineWithoutRateMessage(line));
114
+ }
115
+ return;
116
+ }
75
117
  for (const [index, item] of (accountingDoc.items ?? []).entries()) {
76
118
  if (item.vatPercentage !== 0) {
77
119
  refuse('BR-AE-05', `a reverse charge line has the VAT rate 0, items[${index}].vatPercentage is ${String(item.vatPercentage)}`);
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The code of the package's own check of the German VAT identification number format. It is no
3
+ * rule of EN 16931 or XRechnung; the official rules do not check the format of BT-31.
4
+ */
5
+ export const GERMAN_VAT_ID_FORMAT_CODE = 'EINVOICE-DE-VATID-FORMAT';
6
+
7
+ /**
8
+ * The message of a German VAT identification number whose format is not the one the
9
+ * Bundeszentralamt für Steuern issues (§ 27a Abs. 1 UStG names it as the issuer, not the format)
10
+ * @param vatId The VAT identification number as stated
11
+ */
12
+ export const germanVatIdFormatMessage = (vatId: string): string =>
13
+ `The German VAT identification number (BT-31) is DE followed by nine digits, the format the Bundeszentralamt für Steuern issues; it is "${vatId}"`;
14
+
15
+ /**
16
+ * Whether a VAT identification number with the German prefix DE lacks the format the
17
+ * Bundeszentralamt für Steuern issues, DE followed by nine digits. A number with another prefix, or
18
+ * none, is not checked. The number is taken as it is written, without removing spaces.
19
+ * @param vatId The VAT identification number as stated
20
+ */
21
+ export const isMalformedGermanVatId = (vatId: string | undefined): boolean => {
22
+ const value = (vatId ?? '').trim();
23
+ return value.startsWith('DE') && !/^DE[0-9]{9}$/.test(value);
24
+ };
@@ -488,9 +488,9 @@ export class ConformanceTestHarness {
488
488
  // VAT rules - Reverse charge
489
489
  'BR-AE-01', 'BR-AE-02', 'BR-AE-03', 'BR-AE-04', 'BR-AE-05', 'BR-AE-06', 'BR-AE-07', 'BR-AE-08',
490
490
 
491
- // VAT rules - Intra-community
492
- 'BR-K-01', 'BR-K-02', 'BR-K-03', 'BR-K-04', 'BR-K-05', 'BR-K-06', 'BR-K-07', 'BR-K-08',
493
- 'BR-K-09', 'BR-K-10',
491
+ // VAT rules - Intra-community supply
492
+ 'BR-IC-01', 'BR-IC-02', 'BR-IC-03', 'BR-IC-04', 'BR-IC-05', 'BR-IC-06', 'BR-IC-07', 'BR-IC-08',
493
+ 'BR-IC-09', 'BR-IC-10', 'BR-IC-11', 'BR-IC-12',
494
494
 
495
495
  // VAT rules - Export
496
496
  'BR-G-01', 'BR-G-02', 'BR-G-03', 'BR-G-04', 'BR-G-05', 'BR-G-06', 'BR-G-07', 'BR-G-08',
@@ -511,7 +511,7 @@ export class ConformanceTestHarness {
511
511
  */
512
512
  private getRuleCategory(ruleId: string): keyof CoverageReport['byCategory'] | null {
513
513
  if (ruleId.startsWith('BR-CO-')) return 'calculation';
514
- if (ruleId.match(/^BR-[SZAEKG0]-/)) return 'vat';
514
+ if (ruleId.match(/^BR-(S|Z|E|AE|IC|G|O)-/)) return 'vat';
515
515
  if (ruleId.startsWith('BR-CL-')) return 'codeLists';
516
516
  if (ruleId.match(/^BR-2[0-9]/) || ruleId.match(/^BR-3[0-9]/)) return 'lineLevel';
517
517
  if (ruleId.match(/^BR-[0-9]/) || ruleId.match(/^BR-1[0-9]/)) return 'document';
@@ -548,7 +548,7 @@ export class ConformanceTestHarness {
548
548
  'vat-category-S': ['BR-S-01', 'BR-S-02', 'BR-S-03', 'BR-S-04', 'BR-S-05'],
549
549
  'vat-category-Z': ['BR-Z-01', 'BR-Z-02', 'BR-Z-03', 'BR-Z-04', 'BR-Z-05'],
550
550
  'vat-category-AE': ['BR-AE-01', 'BR-AE-02', 'BR-AE-03', 'BR-AE-04', 'BR-AE-05', 'BR-AE-06'],
551
- 'vat-category-K': ['BR-K-01', 'BR-K-02', 'BR-K-03', 'BR-K-04', 'BR-K-05', 'BR-K-06'],
551
+ 'vat-category-K': ['BR-IC-01', 'BR-IC-02', 'BR-IC-03', 'BR-IC-04', 'BR-IC-05', 'BR-IC-06'],
552
552
  'vat-category-G': ['BR-G-01', 'BR-G-02', 'BR-G-03', 'BR-G-04', 'BR-G-05', 'BR-G-06'],
553
553
  'vat-category-O': ['BR-O-01', 'BR-O-02', 'BR-O-03', 'BR-O-04', 'BR-O-05', 'BR-O-06']
554
554
  };
@@ -247,32 +247,9 @@ export class EN16931BusinessRulesValidator {
247
247
  );
248
248
  }
249
249
 
250
- // BR-CO-16: Amount due for payment = Invoice total with VAT - Paid amount
251
- const paidAmount = useDecimal
252
- ? new Decimal(invoice.metadata?.paidAmount || 0)
253
- : invoice.metadata?.paidAmount || 0;
254
- const expectedDueAmount = useDecimal
255
- ? (expectedGrossTotal as Decimal).subtract(paidAmount)
256
- : (expectedGrossTotal as number) - (paidAmount as number);
257
- const declaredDueAmount = useDecimal
258
- ? new Decimal(invoice.metadata?.amountDue || (useDecimal ? (expectedGrossTotal as Decimal).toNumber() : expectedGrossTotal))
259
- : invoice.metadata?.amountDue || expectedGrossTotal;
260
-
261
- const isDueEqual = useDecimal
262
- ? this.decimalCalculator!.areEqual(expectedDueAmount, declaredDueAmount)
263
- : this.currencyCalculator
264
- ? this.currencyCalculator.areEqual(expectedDueAmount as number, declaredDueAmount as number)
265
- : Math.abs((expectedDueAmount as number) - (declaredDueAmount as number)) < 0.01;
266
-
267
- if (!isDueEqual) {
268
- this.addError(
269
- 'BR-CO-16',
270
- `Amount due (${useDecimal ? (declaredDueAmount as Decimal).toFixed(2) : (declaredDueAmount as number).toFixed(2)}) does not match calculation (${useDecimal ? (expectedDueAmount as Decimal).toFixed(2) : (expectedDueAmount as number).toFixed(2)})`,
271
- 'amountDue',
272
- useDecimal ? (declaredDueAmount as Decimal).toNumber() : declaredDueAmount as number,
273
- useDecimal ? (expectedDueAmount as Decimal).toNumber() : expectedDueAmount as number
274
- );
275
- }
250
+ // BR-CO-16 (amount due for payment = invoice total with VAT - paid amount) is not checked
251
+ // here: the envelope has no amount due (BT-115); the encoders derive it from the gross total
252
+ // and the paid amount (BT-113), and the official rules check it on the written XML
276
253
  }
277
254
 
278
255
  /**
@@ -79,7 +79,7 @@ export class FacturXValidator {
79
79
  'dueDate', // BT-9: Payment due date
80
80
  ],
81
81
  optional: [
82
- 'metadata.buyerReference', // BT-10: Buyer reference
82
+ 'buyerReference', // BT-10: Buyer reference
83
83
  'metadata.purchaseOrderReference', // BT-13: Purchase order reference
84
84
  'metadata.salesOrderReference', // BT-14: Sales order reference
85
85
  'metadata.contractReference', // BT-12: Contract reference
@@ -108,7 +108,7 @@ export class FacturXValidator {
108
108
  // No items required
109
109
  ],
110
110
  optional: [
111
- 'metadata.buyerReference',
111
+ 'buyerReference',
112
112
  'metadata.purchaseOrderReference',
113
113
  'metadata.contractReference',
114
114
  ]
@@ -122,7 +122,7 @@ export class FacturXValidator {
122
122
  'issueDate',
123
123
  'accountingDocType',
124
124
  'currency',
125
- 'metadata.buyerReference',
125
+ 'buyerReference',
126
126
 
127
127
  // Seller information
128
128
  'from.name',
@@ -411,12 +411,12 @@ export class FacturXValidator {
411
411
 
412
412
  case FacturXProfile.EN16931:
413
413
  // EN16931 requires full compliance - additional checks handled by EN16931 validator
414
- if (!invoice.metadata?.buyerReference && !invoice.metadata?.extensions?.purchaseOrderReference) {
414
+ if (!invoice.buyerReference && !invoice.metadata?.extensions?.purchaseOrderReference) {
415
415
  results.push({
416
416
  ruleId: 'FX-EN-01',
417
417
  severity: 'error',
418
418
  message: 'EN16931 profile requires either buyer reference or purchase order reference',
419
- field: 'metadata.buyerReference',
419
+ field: 'buyerReference',
420
420
  source: 'FACTURX'
421
421
  });
422
422
  }
@@ -12,6 +12,7 @@ import { CodeListValidator } from './codelist.validator.js';
12
12
  import type { ValidationResult, ValidationOptions, ValidationReport } from './validation.types.js';
13
13
  import type { EInvoice } from '../../einvoice.js';
14
14
  import { UBL_CUSTOMIZATION_IDS } from '../ubl/ubl.types.js';
15
+ import { isPeppolBisBilling3 } from '../utils/peppol.profile.js';
15
16
 
16
17
  /**
17
18
  * Main validator that combines all validation capabilities
@@ -172,8 +173,7 @@ export class MainValidator {
172
173
  return 'XRECHNUNG_3.0';
173
174
  }
174
175
 
175
- if (profileId.includes('peppol') || customizationId.includes('peppol') ||
176
- profileId.includes('urn:fdc:peppol.eu')) {
176
+ if (isPeppolBisBilling3(customizationId)) {
177
177
  return 'PEPPOL_BIS_3.0';
178
178
  }
179
179
 
@@ -215,20 +215,7 @@ export class MainValidator {
215
215
  * Check if invoice is PEPPOL
216
216
  */
217
217
  private isPeppolInvoice(invoice: EInvoice): boolean {
218
- const profileId = invoice.metadata?.profileId || '';
219
- const customizationId = invoice.metadata?.customizationId || '';
220
-
221
- const peppolProfiles = [
222
- 'urn:fdc:peppol.eu:2017:poacc:billing:3.0',
223
- 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
224
- 'peppol-bis-3',
225
- 'peppol'
226
- ];
227
-
228
- return peppolProfiles.some(profile =>
229
- profileId.toLowerCase().includes(profile.toLowerCase()) ||
230
- customizationId.toLowerCase().includes(profile.toLowerCase())
231
- );
218
+ return isPeppolBisBilling3(invoice.metadata?.customizationId);
232
219
  }
233
220
 
234
221
  /**
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ValidationResult } from './validation.types.js';
7
7
  import type { EInvoice } from '../../einvoice.js';
8
+ import { isPeppolBisBilling3 } from '../utils/peppol.profile.js';
8
9
 
9
10
  /**
10
11
  * PEPPOL BIS 3.0 Validator
@@ -50,20 +51,8 @@ export class PeppolValidator {
50
51
  * Check if invoice is PEPPOL
51
52
  */
52
53
  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
- );
54
+ // the PEPPOL BIS Billing 3.0 specification identifier (BT-24), not a derivative that names PEPPOL
55
+ return isPeppolBisBilling3(invoice.metadata?.customizationId);
67
56
  }
68
57
 
69
58
  /**
@@ -393,12 +382,12 @@ export class PeppolValidator {
393
382
 
394
383
  // PEPPOL-B-01: Invoice must have a buyer reference or purchase order reference
395
384
  const purchaseOrderRef = invoice.metadata?.extensions?.purchaseOrderReference;
396
- if (!invoice.metadata?.buyerReference && !purchaseOrderRef) {
385
+ if (!invoice.buyerReference && !purchaseOrderRef) {
397
386
  results.push({
398
387
  ruleId: 'PEPPOL-B-01',
399
388
  severity: 'error',
400
389
  message: 'Invoice must have either a buyer reference (BT-10) or purchase order reference (BT-13)',
401
- field: 'metadata.buyerReference',
390
+ field: 'buyerReference',
402
391
  source: 'PEPPOL'
403
392
  });
404
393
  }
@@ -2,10 +2,16 @@
2
2
  * Enhanced validation types for EN16931 compliance
3
3
  */
4
4
 
5
+ /**
6
+ * The source of a check of the package's own, one no official rule set states
7
+ * (such as `EINVOICE-DE-VATID-FORMAT`), so it is not attributed to a standard
8
+ */
9
+ export const EINVOICE_RULE_SOURCE = 'EINVOICE';
10
+
5
11
  export interface ValidationResult {
6
12
  // Core identification
7
13
  ruleId: string; // e.g., "BR-CO-14"
8
- source: string; // e.g., "EN16931", "PEPPOL", "XRECHNUNG"
14
+ source: string; // e.g., "EN16931", "PEPPOL", "XRECHNUNG"; EINVOICE_RULE_SOURCE for a check of the package's own
9
15
  severity: 'error' | 'warning' | 'info';
10
16
  message: string;
11
17