@fin.cx/einvoice 8.0.0 → 8.2.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 (93) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/einvoice.d.ts +14 -1
  3. package/dist_ts/einvoice.js +48 -3
  4. package/dist_ts/formats/base/base.encoder.d.ts +1 -1
  5. package/dist_ts/formats/base/base.validator.d.ts +5 -0
  6. package/dist_ts/formats/base/base.validator.js +28 -1
  7. package/dist_ts/formats/cii/cii.decoder.d.ts +12 -1
  8. package/dist_ts/formats/cii/cii.decoder.js +34 -1
  9. package/dist_ts/formats/cii/cii.encoder.d.ts +14 -3
  10. package/dist_ts/formats/cii/cii.encoder.js +58 -6
  11. package/dist_ts/formats/cii/cii.types.d.ts +1 -0
  12. package/dist_ts/formats/cii/cii.types.js +3 -2
  13. package/dist_ts/formats/cii/cii.validator.d.ts +11 -0
  14. package/dist_ts/formats/cii/cii.validator.js +35 -1
  15. package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +1 -1
  16. package/dist_ts/formats/cii/facturx/facturx.decoder.js +11 -5
  17. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +2 -2
  18. package/dist_ts/formats/cii/facturx/facturx.encoder.js +17 -11
  19. package/dist_ts/formats/cii/facturx/facturx.validator.js +8 -4
  20. package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +1 -1
  21. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +11 -5
  22. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +1 -1
  23. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +20 -14
  24. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +7 -1
  25. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +29 -5
  26. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +5 -4
  27. package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +5 -0
  28. package/dist_ts/formats/converters/xml-to-einvoice.converter.js +11 -2
  29. package/dist_ts/formats/semantic/semantic.adapter.js +2 -2
  30. package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -1
  31. package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +23 -1
  32. package/dist_ts/formats/ubl/generic/ubl.encoder.js +71 -12
  33. package/dist_ts/formats/ubl/ubl.decoder.d.ts +1 -1
  34. package/dist_ts/formats/ubl/ubl.encoder.d.ts +16 -3
  35. package/dist_ts/formats/ubl/ubl.encoder.js +75 -13
  36. package/dist_ts/formats/ubl/ubl.validator.d.ts +11 -0
  37. package/dist_ts/formats/ubl/ubl.validator.js +35 -1
  38. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +6 -1
  39. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +39 -8
  40. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +1 -1
  41. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +4 -2
  42. package/dist_ts/formats/utils/date.value.d.ts +10 -0
  43. package/dist_ts/formats/utils/date.value.js +21 -0
  44. package/dist_ts/formats/utils/document.typecode.d.ts +3 -3
  45. package/dist_ts/formats/utils/document.typecode.js +8 -4
  46. package/dist_ts/formats/utils/preceding.invoice.d.ts +26 -0
  47. package/dist_ts/formats/utils/preceding.invoice.js +52 -0
  48. package/dist_ts/formats/utils/unit.codes.d.ts +40 -0
  49. package/dist_ts/formats/utils/unit.codes.js +193 -0
  50. package/dist_ts/formats/validation/codelist.validator.d.ts +1 -1
  51. package/dist_ts/formats/validation/codelist.validator.js +7 -9
  52. package/dist_ts/formats/validation/en16931.business-rules.validator.js +13 -13
  53. package/dist_ts/formats/validation/validation.types.d.ts +0 -4
  54. package/dist_ts/formats/validation/validation.types.js +1 -28
  55. package/dist_ts/index.d.ts +5 -1
  56. package/dist_ts/index.js +6 -1
  57. package/dist_ts/interfaces/common.d.ts +6 -2
  58. package/dist_ts_install/download-schematron.js +3 -5
  59. package/package.json +8 -8
  60. package/readme.md +103 -10
  61. package/ts/00_commitinfo_data.ts +1 -1
  62. package/ts/einvoice.ts +51 -2
  63. package/ts/formats/base/base.encoder.ts +1 -1
  64. package/ts/formats/base/base.validator.ts +28 -0
  65. package/ts/formats/cii/cii.decoder.ts +43 -1
  66. package/ts/formats/cii/cii.encoder.ts +62 -6
  67. package/ts/formats/cii/cii.types.ts +2 -1
  68. package/ts/formats/cii/cii.validator.ts +47 -0
  69. package/ts/formats/cii/facturx/facturx.decoder.ts +11 -4
  70. package/ts/formats/cii/facturx/facturx.encoder.ts +17 -10
  71. package/ts/formats/cii/facturx/facturx.validator.ts +9 -3
  72. package/ts/formats/cii/zugferd/zugferd.decoder.ts +11 -4
  73. package/ts/formats/cii/zugferd/zugferd.encoder.ts +20 -13
  74. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +32 -4
  75. package/ts/formats/cii/zugferd/zugferd.validator.ts +4 -3
  76. package/ts/formats/converters/xml-to-einvoice.converter.ts +11 -1
  77. package/ts/formats/semantic/semantic.adapter.ts +1 -1
  78. package/ts/formats/ubl/en16931.ubl.validator.ts +6 -0
  79. package/ts/formats/ubl/generic/ubl.encoder.ts +78 -11
  80. package/ts/formats/ubl/ubl.decoder.ts +1 -1
  81. package/ts/formats/ubl/ubl.encoder.ts +77 -13
  82. package/ts/formats/ubl/ubl.validator.ts +47 -0
  83. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +47 -7
  84. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +4 -1
  85. package/ts/formats/utils/date.value.ts +21 -0
  86. package/ts/formats/utils/document.typecode.ts +7 -3
  87. package/ts/formats/utils/preceding.invoice.ts +74 -0
  88. package/ts/formats/utils/unit.codes.ts +215 -0
  89. package/ts/formats/validation/codelist.validator.ts +8 -19
  90. package/ts/formats/validation/en16931.business-rules.validator.ts +13 -13
  91. package/ts/formats/validation/validation.types.ts +0 -28
  92. package/ts/index.ts +12 -0
  93. package/ts/interfaces/common.ts +6 -1
@@ -8,6 +8,10 @@ import type {
8
8
  import { business, finance } from '../../../plugins.js';
9
9
  import { UBLDocumentType } from '../ubl.types.js';
10
10
  import { EN16931Validator } from '../../validation/en16931.validator.js';
11
+ import {
12
+ getPrecedingInvoiceFields,
13
+ type TPrecedingInvoiceReference,
14
+ } from '../../utils/preceding.invoice.js';
11
15
 
12
16
  /**
13
17
  * Decoder for XRechnung (UBL) format
@@ -23,7 +27,7 @@ export class XRechnungDecoder extends UBLBaseDecoder {
23
27
  }
24
28
 
25
29
  /**
26
- * Decodes a UBL invoice document: an invoice, a debit note or a self-billed invoice
30
+ * Decodes a UBL invoice document: an invoice, a corrected invoice, a debit note or a self-billed invoice
27
31
  * @param accountingDocType The type the document type code stands for
28
32
  * @returns Promise resolving to the invoice document
29
33
  */
@@ -83,7 +87,8 @@ export class XRechnungDecoder extends UBLBaseDecoder {
83
87
  const buyerItemID = this.getText('./cac:Item/cac:BuyersItemIdentification/cbc:ID', line) || '';
84
88
  const standardItemID = this.getText('./cac:Item/cac:StandardItemIdentification/cbc:ID', line) || '';
85
89
  const commodityClassification = this.getText('./cac:Item/cac:CommodityClassification/cbc:ItemClassificationCode', line) || '';
86
- const unitType = this.getText(`./${quantityElement}/@unitCode`, line) || 'EA';
90
+ // an absent unit code (BT-130) stays absent, so that BR-23 reports it
91
+ const unitType = this.getText(`./${quantityElement}/@unitCode`, line);
87
92
 
88
93
  let unitQuantity = 1;
89
94
  const quantityText = this.getText(`./${quantityElement}`, line);
@@ -167,10 +172,11 @@ export class XRechnungDecoder extends UBLBaseDecoder {
167
172
  const paymentTermsNote = this.getText('//cac:PaymentTerms/cbc:Note', this.doc);
168
173
  const discountPercent = this.getText('//cac:PaymentTerms/cbc:SettlementDiscountPercent', this.doc);
169
174
 
170
- // Extract period information
171
- const periodStart = this.getText('//cac:InvoicePeriod/cbc:StartDate', this.doc);
172
- const periodEnd = this.getText('//cac:InvoicePeriod/cbc:EndDate', this.doc);
173
- const deliveryDate = this.getText('//cac:Delivery/cbc:ActualDeliveryDate', this.doc);
175
+ // Extract the document level period (BG-14) and delivery date (BT-72); a line has
176
+ // an invoice period (BG-26) and a delivery of its own, which are not the document's
177
+ const periodStart = this.getText('/*/cac:InvoicePeriod/cbc:StartDate', this.doc);
178
+ const periodEnd = this.getText('/*/cac:InvoicePeriod/cbc:EndDate', this.doc);
179
+ const deliveryDate = this.getText('/*/cac:Delivery/cbc:ActualDeliveryDate', this.doc);
174
180
 
175
181
  // Extract notes (excluding PaymentTerms notes)
176
182
  const allNotes: string[] = [];
@@ -254,7 +260,18 @@ export class XRechnungDecoder extends UBLBaseDecoder {
254
260
  reverseCharge: false,
255
261
  currency: currencyCode as finance.TCurrency,
256
262
  notes: notes,
257
- objectActions: []
263
+ objectActions: [],
264
+ // the day and the period of supply as the document states them (BT-72, BG-14)
265
+ ...(deliveryDate ? { deliveryDate: this.parseRequiredUblDate(deliveryDate) } : {}),
266
+ ...(periodStart && periodEnd
267
+ ? {
268
+ periodOfPerformance: {
269
+ from: this.parseRequiredUblDate(periodStart),
270
+ to: this.parseRequiredUblDate(periodEnd),
271
+ },
272
+ }
273
+ : {}),
274
+ ...getPrecedingInvoiceFields(accountingDocType, this.extractPrecedingInvoiceReferences()),
258
275
  };
259
276
 
260
277
  if (hasBusinessReferences || hasPaymentInformation || hasDateInformation) {
@@ -280,6 +297,29 @@ export class XRechnungDecoder extends UBLBaseDecoder {
280
297
  }
281
298
  }
282
299
 
300
+ /**
301
+ * Reads the preceding invoice references (BG-3): the number (BT-25) and,
302
+ * when stated, the issue date (BT-26) of each.
303
+ */
304
+ private extractPrecedingInvoiceReferences(): TPrecedingInvoiceReference[] {
305
+ const referenceNodes = this.select(
306
+ './cac:BillingReference/cac:InvoiceDocumentReference',
307
+ this.doc.documentElement,
308
+ );
309
+ const references: TPrecedingInvoiceReference[] = [];
310
+ for (const referenceNode of Array.isArray(referenceNodes) ? referenceNodes : []) {
311
+ const documentId = this.getText('./cbc:ID', referenceNode).trim();
312
+ if (!documentId) {
313
+ continue;
314
+ }
315
+ const issueDate = this.getText('./cbc:IssueDate', referenceNode).trim();
316
+ references.push(
317
+ issueDate ? { documentId, issueDate: this.parseRequiredUblDate(issueDate) } : { documentId },
318
+ );
319
+ }
320
+ return references;
321
+ }
322
+
283
323
  /**
284
324
  * Extracts party information from XML
285
325
  * @param partyPath XPath to the party element
@@ -25,7 +25,7 @@ export class XRechnungEncoder extends UBLEncoder {
25
25
  }
26
26
 
27
27
  /**
28
- * Encodes an invoice document (invoice, debit note or self-billed invoice) into XRechnung XML
28
+ * Encodes an invoice document (invoice, corrected invoice, debit note or self-billed invoice) into XRechnung XML
29
29
  * @param invoice Invoice document to encode
30
30
  * @returns XRechnung XML string
31
31
  */
@@ -114,6 +114,9 @@ export class XRechnungEncoder extends UBLEncoder {
114
114
 
115
115
  // Enhance line items with metadata
116
116
  this.enhanceLineItems(doc, invoice);
117
+
118
+ // the customizations above insert where they can; the schema order is restored last
119
+ this.orderRootElements(doc);
117
120
  }
118
121
 
119
122
  /**
@@ -0,0 +1,21 @@
1
+ import { EInvoiceFormatError } from '../../errors.js';
2
+
3
+ /**
4
+ * The date an encoder is about to write, as a `Date`. A value that is no
5
+ * finite timestamp, or lies outside the range of a JavaScript date, is refused
6
+ * with an `EInvoiceFormatError` naming the field: the encoders never write a
7
+ * date the document does not state, such as today in place of a missing one.
8
+ * @param timestamp The timestamp (milliseconds since the epoch) to write
9
+ * @param field The business term it is written as, for example `BT-2 invoice issue date`
10
+ * @param targetFormat The format being written
11
+ */
12
+ export const getWritableDate = (timestamp: unknown, field: string, targetFormat: string): Date => {
13
+ const date = typeof timestamp === 'number' && Number.isFinite(timestamp) ? new Date(timestamp) : undefined;
14
+ if (!date || Number.isNaN(date.getTime())) {
15
+ throw new EInvoiceFormatError(`${field} is no valid date: ${String(timestamp)}`, {
16
+ targetFormat,
17
+ unsupportedFeatures: [field],
18
+ });
19
+ }
20
+ return date;
21
+ };
@@ -7,6 +7,7 @@ import { EInvoiceFormatError } from '../../errors.js';
7
7
  */
8
8
  const DOCUMENT_TYPE_CODES: Record<TAccountingDocType, string> = {
9
9
  'invoice': '380',
10
+ 'corrected-invoice': '384',
10
11
  'creditnote': '381',
11
12
  'debitnote': '383',
12
13
  'self-billed-invoice': '389',
@@ -42,9 +43,9 @@ export const getDocumentTypeCode = (accountingDocType: TAccountingDocType): stri
42
43
  };
43
44
 
44
45
  /**
45
- * The accounting document type of a decoded type code. 383 and 389 have a
46
- * type of their own; the remaining credit note related codes are credit
47
- * notes; everything else is an invoice, which covers 380, 384 and the other
46
+ * The accounting document type of a decoded type code. 383, 384 and 389 have
47
+ * a type of their own; the remaining credit note related codes are credit
48
+ * notes; everything else is an invoice, which covers 380 and the other
48
49
  * invoice related codes (326, 386, 875, ...) the envelope has no name for.
49
50
  * A missing or unlisted code stays an invoice as well: the document is an
50
51
  * invoice document, and reporting the code is the validators' job (BR-04,
@@ -55,6 +56,9 @@ export const getAccountingDocType = (typeCode: string): TAccountingDocType => {
55
56
  if (code === DOCUMENT_TYPE_CODES['debitnote']) {
56
57
  return 'debitnote';
57
58
  }
59
+ if (code === DOCUMENT_TYPE_CODES['corrected-invoice']) {
60
+ return 'corrected-invoice';
61
+ }
58
62
  if (code === DOCUMENT_TYPE_CODES['self-billed-invoice']) {
59
63
  return 'self-billed-invoice';
60
64
  }
@@ -0,0 +1,74 @@
1
+ import type {
2
+ TAccountingDoc,
3
+ TAccountingDocType,
4
+ TCorrectedInvoiceReference,
5
+ TRelatedDocument,
6
+ } from '../../interfaces/common.js';
7
+
8
+ /**
9
+ * One preceding invoice reference (BG-3): the number of the preceding invoice
10
+ * (BT-25) and, when the document states it, its issue date (BT-26).
11
+ */
12
+ export type TPrecedingInvoiceReference = TCorrectedInvoiceReference;
13
+
14
+ /**
15
+ * The preceding invoice references (BG-3) an encoder writes for a document,
16
+ * taken only from what the document states:
17
+ * - a corrected invoice: the invoice it corrects (`correctedInvoice`);
18
+ * - a corrected invoice or a credit note: every related document with the
19
+ * relation `'corrects'`.
20
+ * Other documents and other relations write none.
21
+ */
22
+ export const getPrecedingInvoiceReferences = (
23
+ accountingDoc: TAccountingDoc,
24
+ ): TPrecedingInvoiceReference[] => {
25
+ const references: TPrecedingInvoiceReference[] = [];
26
+ if (accountingDoc.accountingDocType === 'corrected-invoice' && accountingDoc.correctedInvoice) {
27
+ references.push(accountingDoc.correctedInvoice);
28
+ }
29
+ if (
30
+ accountingDoc.accountingDocType === 'corrected-invoice' ||
31
+ accountingDoc.accountingDocType === 'creditnote'
32
+ ) {
33
+ for (const relatedDocument of accountingDoc.relatedDocuments ?? []) {
34
+ if (relatedDocument.relationType === 'corrects') {
35
+ references.push(
36
+ relatedDocument.issueDate === undefined
37
+ ? { documentId: relatedDocument.documentId }
38
+ : { documentId: relatedDocument.documentId, issueDate: relatedDocument.issueDate },
39
+ );
40
+ }
41
+ }
42
+ }
43
+ return references;
44
+ };
45
+
46
+ /**
47
+ * The envelope fields for the preceding invoice references a decoder read, the
48
+ * inverse of {@link getPrecedingInvoiceReferences}: on a corrected invoice the
49
+ * first reference is the invoice it corrects and further ones are related
50
+ * documents it corrects; on a credit note every reference is a related
51
+ * document it corrects. Other documents keep no reference, as before.
52
+ */
53
+ export const getPrecedingInvoiceFields = (
54
+ accountingDocType: TAccountingDocType,
55
+ references: TPrecedingInvoiceReference[],
56
+ ): { correctedInvoice?: TCorrectedInvoiceReference; relatedDocuments?: TRelatedDocument[] } => {
57
+ const asRelated = (reference: TPrecedingInvoiceReference): TRelatedDocument => ({
58
+ relationType: 'corrects',
59
+ ...reference,
60
+ });
61
+ if (references.length === 0) {
62
+ return {};
63
+ }
64
+ if (accountingDocType === 'corrected-invoice') {
65
+ const [correctedInvoice, ...furtherReferences] = references;
66
+ return furtherReferences.length === 0
67
+ ? { correctedInvoice }
68
+ : { correctedInvoice, relatedDocuments: furtherReferences.map(asRelated) };
69
+ }
70
+ if (accountingDocType === 'creditnote') {
71
+ return { relatedDocuments: references.map(asRelated) };
72
+ }
73
+ return {};
74
+ };
@@ -0,0 +1,215 @@
1
+ /**
2
+ * The unit of measure codes EN 16931 admits, as business rule BR-CL-23 lists them: UN/ECE
3
+ * Recommendation 20 with the Recommendation 21 extension. They apply to the invoiced quantity
4
+ * unit of measure (BT-130) and the item price base quantity unit of measure (BT-150).
5
+ *
6
+ * Source: the EN 16931 validation artefacts of CEN/TC 434, release validation-1.3.16
7
+ * (https://github.com/ConnectingEurope/eInvoicing-EN16931/releases/tag/validation-1.3.16),
8
+ * rule BR-CL-23 in ubl/schematron/codelist/EN16931-UBL-codes.sch and
9
+ * cii/schematron/codelist/EN16931-CII-codes.sch, which carry the same 2162 codes. The codes are
10
+ * reproduced unaltered and in the order of the rule. The source files are licensed under EUPL-1.2;
11
+ * the codes are those of UN/ECE Recommendations 20 and 21.
12
+ */
13
+
14
+ /** Where the EN 16931 unit code list comes from, and the codes it holds. */
15
+ export interface IEn16931UnitCodeList {
16
+ /** the business rule that lists the codes */
17
+ readonly rule: 'BR-CL-23';
18
+ /** the business terms whose values the rule checks */
19
+ readonly businessTerms: readonly ['BT-130', 'BT-150'];
20
+ /** the code lists the codes belong to */
21
+ readonly codeLists: 'UN/ECE Recommendation 20 with Recommendation 21 extension';
22
+ /** the release of the EN 16931 validation artefacts the codes are taken from */
23
+ readonly source: 'EN 16931 validation artefacts (CEN/TC 434) validation-1.3.16';
24
+ /**
25
+ * the codes, in the order of the rule; the array is frozen, and the validators check against a
26
+ * private copy, so nothing a caller does to it changes a check
27
+ */
28
+ readonly codes: readonly string[];
29
+ }
30
+
31
+ const BR_CL_23_CODES: readonly string[] = Object.freeze([
32
+ '10', '11', '13', '14', '15', '20', '21', '22', '23', '24', '25', '27', '28', '33',
33
+ '34', '35', '37', '38', '40', '41', '56', '57', '58', '59', '60', '61', '74', '77',
34
+ '80', '81', '85', '87', '89', '91', '1I', '2A', '2B', '2C', '2G', '2H', '2I', '2J',
35
+ '2K', '2L', '2M', '2N', '2P', '2Q', '2R', '2U', '2X', '2Y', '2Z', '3B', '3C', '4C',
36
+ '4G', '4H', '4K', '4L', '4M', '4N', '4O', '4P', '4Q', '4R', '4T', '4U', '4W', '4X',
37
+ '5A', '5B', '5E', '5J', 'A10', 'A11', 'A12', 'A13', 'A14', 'A15', 'A16', 'A17', 'A18', 'A19',
38
+ 'A2', 'A20', 'A21', 'A22', 'A23', 'A24', 'A26', 'A27', 'A28', 'A29', 'A3', 'A30', 'A31', 'A32',
39
+ 'A33', 'A34', 'A35', 'A36', 'A37', 'A38', 'A39', 'A4', 'A40', 'A41', 'A42', 'A43', 'A44', 'A45',
40
+ 'A47', 'A48', 'A49', 'A5', 'A53', 'A54', 'A55', 'A56', 'A59', 'A6', 'A68', 'A69', 'A7', 'A70',
41
+ 'A71', 'A73', 'A74', 'A75', 'A76', 'A8', 'A84', 'A85', 'A86', 'A87', 'A88', 'A89', 'A9', 'A90',
42
+ 'A91', 'A93', 'A94', 'A95', 'A96', 'A97', 'A98', 'A99', 'AA', 'AB', 'ACR', 'ACT', 'AD', 'AE',
43
+ 'AH', 'AI', 'AK', 'AL', 'AMH', 'AMP', 'ANN', 'APZ', 'AQ', 'AS', 'ASM', 'ASU', 'ATM', 'AWG',
44
+ 'AY', 'AZ', 'B1', 'B10', 'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'B18', 'B19', 'B20',
45
+ 'B21', 'B22', 'B23', 'B24', 'B25', 'B26', 'B27', 'B28', 'B29', 'B3', 'B30', 'B31', 'B32', 'B33',
46
+ 'B34', 'B35', 'B4', 'B41', 'B42', 'B43', 'B44', 'B45', 'B46', 'B47', 'B48', 'B49', 'B50', 'B52',
47
+ 'B53', 'B54', 'B55', 'B56', 'B57', 'B58', 'B59', 'B60', 'B61', 'B62', 'B63', 'B64', 'B66', 'B67',
48
+ 'B68', 'B69', 'B7', 'B70', 'B71', 'B72', 'B73', 'B74', 'B75', 'B76', 'B77', 'B78', 'B79', 'B8',
49
+ 'B80', 'B81', 'B82', 'B83', 'B84', 'B85', 'B86', 'B87', 'B88', 'B89', 'B90', 'B91', 'B92', 'B93',
50
+ 'B94', 'B95', 'B96', 'B97', 'B98', 'B99', 'BAR', 'BB', 'BFT', 'BHP', 'BIL', 'BLD', 'BLL', 'BP',
51
+ 'BPM', 'BQL', 'BTU', 'BUA', 'BUI', 'C0', 'C10', 'C11', 'C12', 'C13', 'C14', 'C15', 'C16', 'C17',
52
+ 'C18', 'C19', 'C20', 'C21', 'C22', 'C23', 'C24', 'C25', 'C26', 'C27', 'C28', 'C29', 'C3', 'C30',
53
+ 'C31', 'C32', 'C33', 'C34', 'C35', 'C36', 'C37', 'C38', 'C39', 'C40', 'C41', 'C42', 'C43', 'C44',
54
+ 'C45', 'C46', 'C47', 'C48', 'C49', 'C50', 'C51', 'C52', 'C53', 'C54', 'C55', 'C56', 'C57', 'C58',
55
+ 'C59', 'C60', 'C61', 'C62', 'C63', 'C64', 'C65', 'C66', 'C67', 'C68', 'C69', 'C7', 'C70', 'C71',
56
+ 'C72', 'C73', 'C74', 'C75', 'C76', 'C78', 'C79', 'C8', 'C80', 'C81', 'C82', 'C83', 'C84', 'C85',
57
+ 'C86', 'C87', 'C88', 'C89', 'C9', 'C90', 'C91', 'C92', 'C93', 'C94', 'C95', 'C96', 'C97', 'C99',
58
+ 'CCT', 'CDL', 'CEL', 'CEN', 'CG', 'CGM', 'CKG', 'CLF', 'CLT', 'CMK', 'CMQ', 'CMT', 'CNP', 'CNT',
59
+ 'COU', 'CTG', 'CTM', 'CTN', 'CUR', 'CWA', 'CWI', 'D03', 'D04', 'D1', 'D10', 'D11', 'D12', 'D13',
60
+ 'D15', 'D16', 'D17', 'D18', 'D19', 'D2', 'D20', 'D21', 'D22', 'D23', 'D24', 'D25', 'D26', 'D27',
61
+ 'D29', 'D30', 'D31', 'D32', 'D33', 'D34', 'D36', 'D41', 'D42', 'D43', 'D44', 'D45', 'D46', 'D47',
62
+ 'D48', 'D49', 'D5', 'D50', 'D51', 'D52', 'D53', 'D54', 'D55', 'D56', 'D57', 'D58', 'D59', 'D6',
63
+ 'D60', 'D61', 'D62', 'D63', 'D65', 'D68', 'D69', 'D73', 'D74', 'D77', 'D78', 'D80', 'D81', 'D82',
64
+ 'D83', 'D85', 'D86', 'D87', 'D88', 'D89', 'D91', 'D93', 'D94', 'D95', 'DAA', 'DAD', 'DAY', 'DB',
65
+ 'DBM', 'DBW', 'DD', 'DEC', 'DG', 'DJ', 'DLT', 'DMA', 'DMK', 'DMO', 'DMQ', 'DMT', 'DN', 'DPC',
66
+ 'DPR', 'DPT', 'DRA', 'DRI', 'DRL', 'DT', 'DTN', 'DWT', 'DZN', 'DZP', 'E01', 'E07', 'E08', 'E09',
67
+ 'E10', 'E12', 'E14', 'E15', 'E16', 'E17', 'E18', 'E19', 'E20', 'E21', 'E22', 'E23', 'E25', 'E27',
68
+ 'E28', 'E30', 'E31', 'E32', 'E33', 'E34', 'E35', 'E36', 'E37', 'E38', 'E39', 'E4', 'E40', 'E41',
69
+ 'E42', 'E43', 'E44', 'E45', 'E46', 'E47', 'E48', 'E49', 'E50', 'E51', 'E52', 'E53', 'E54', 'E55',
70
+ 'E56', 'E57', 'E58', 'E59', 'E60', 'E61', 'E62', 'E63', 'E64', 'E65', 'E66', 'E67', 'E68', 'E69',
71
+ 'E70', 'E71', 'E72', 'E73', 'E74', 'E75', 'E76', 'E77', 'E78', 'E79', 'E80', 'E81', 'E82', 'E83',
72
+ 'E84', 'E85', 'E86', 'E87', 'E88', 'E89', 'E90', 'E91', 'E92', 'E93', 'E94', 'E95', 'E96', 'E97',
73
+ 'E98', 'E99', 'EA', 'EB', 'EQ', 'F01', 'F02', 'F03', 'F04', 'F05', 'F06', 'F07', 'F08', 'F10',
74
+ 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24',
75
+ 'F25', 'F26', 'F27', 'F28', 'F29', 'F30', 'F31', 'F32', 'F33', 'F34', 'F35', 'F36', 'F37', 'F38',
76
+ 'F39', 'F40', 'F41', 'F42', 'F43', 'F44', 'F45', 'F46', 'F47', 'F48', 'F49', 'F50', 'F51', 'F52',
77
+ 'F53', 'F54', 'F55', 'F56', 'F57', 'F58', 'F59', 'F60', 'F61', 'F62', 'F63', 'F64', 'F65', 'F66',
78
+ 'F67', 'F68', 'F69', 'F70', 'F71', 'F72', 'F73', 'F74', 'F75', 'F76', 'F77', 'F78', 'F79', 'F80',
79
+ 'F81', 'F82', 'F83', 'F84', 'F85', 'F86', 'F87', 'F88', 'F89', 'F90', 'F91', 'F92', 'F93', 'F94',
80
+ 'F95', 'F96', 'F97', 'F98', 'F99', 'FAH', 'FAR', 'FBM', 'FC', 'FF', 'FH', 'FIT', 'FL', 'FNU',
81
+ 'FOT', 'FP', 'FR', 'FS', 'FTK', 'FTQ', 'G01', 'G04', 'G05', 'G06', 'G08', 'G09', 'G10', 'G11',
82
+ 'G12', 'G13', 'G14', 'G15', 'G16', 'G17', 'G18', 'G19', 'G2', 'G20', 'G21', 'G23', 'G24', 'G25',
83
+ 'G26', 'G27', 'G28', 'G29', 'G3', 'G30', 'G31', 'G32', 'G33', 'G34', 'G35', 'G36', 'G37', 'G38',
84
+ 'G39', 'G40', 'G41', 'G42', 'G43', 'G44', 'G45', 'G46', 'G47', 'G48', 'G49', 'G50', 'G51', 'G52',
85
+ 'G53', 'G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G60', 'G61', 'G62', 'G63', 'G64', 'G65', 'G66',
86
+ 'G67', 'G68', 'G69', 'G70', 'G71', 'G72', 'G73', 'G74', 'G75', 'G76', 'G77', 'G78', 'G79', 'G80',
87
+ 'G81', 'G82', 'G83', 'G84', 'G85', 'G86', 'G87', 'G88', 'G89', 'G90', 'G91', 'G92', 'G93', 'G94',
88
+ 'G95', 'G96', 'G97', 'G98', 'G99', 'GB', 'GBQ', 'GDW', 'GE', 'GF', 'GFI', 'GGR', 'GIA', 'GIC',
89
+ 'GII', 'GIP', 'GJ', 'GL', 'GLD', 'GLI', 'GLL', 'GM', 'GO', 'GP', 'GQ', 'GRM', 'GRN', 'GRO',
90
+ 'GV', 'GWH', 'H03', 'H04', 'H05', 'H06', 'H07', 'H08', 'H09', 'H10', 'H11', 'H12', 'H13', 'H14',
91
+ 'H15', 'H16', 'H18', 'H19', 'H20', 'H21', 'H22', 'H23', 'H24', 'H25', 'H26', 'H27', 'H28', 'H29',
92
+ 'H30', 'H31', 'H32', 'H33', 'H34', 'H35', 'H36', 'H37', 'H38', 'H39', 'H40', 'H41', 'H42', 'H43',
93
+ 'H44', 'H45', 'H46', 'H47', 'H48', 'H49', 'H50', 'H51', 'H52', 'H53', 'H54', 'H55', 'H56', 'H57',
94
+ 'H58', 'H59', 'H60', 'H61', 'H62', 'H63', 'H64', 'H65', 'H66', 'H67', 'H68', 'H69', 'H70', 'H71',
95
+ 'H72', 'H73', 'H74', 'H75', 'H76', 'H77', 'H79', 'H80', 'H81', 'H82', 'H83', 'H84', 'H85', 'H87',
96
+ 'H88', 'H89', 'H90', 'H91', 'H92', 'H93', 'H94', 'H95', 'H96', 'H98', 'H99', 'HA', 'HAD', 'HBA',
97
+ 'HBX', 'HC', 'HDW', 'HEA', 'HGM', 'HH', 'HIU', 'HKM', 'HLT', 'HM', 'HMO', 'HMQ', 'HMT', 'HPA',
98
+ 'HTZ', 'HUR', 'HWE', 'IA', 'IE', 'INH', 'INK', 'INQ', 'ISD', 'IU', 'IUG', 'IV', 'J10', 'J12',
99
+ 'J13', 'J14', 'J15', 'J16', 'J17', 'J18', 'J19', 'J2', 'J20', 'J21', 'J22', 'J23', 'J24', 'J25',
100
+ 'J26', 'J27', 'J28', 'J29', 'J30', 'J31', 'J32', 'J33', 'J34', 'J35', 'J36', 'J38', 'J39', 'J40',
101
+ 'J41', 'J42', 'J43', 'J44', 'J45', 'J46', 'J47', 'J48', 'J49', 'J50', 'J51', 'J52', 'J53', 'J54',
102
+ 'J55', 'J56', 'J57', 'J58', 'J59', 'J60', 'J61', 'J62', 'J63', 'J64', 'J65', 'J66', 'J67', 'J68',
103
+ 'J69', 'J70', 'J71', 'J72', 'J73', 'J74', 'J75', 'J76', 'J78', 'J79', 'J81', 'J82', 'J83', 'J84',
104
+ 'J85', 'J87', 'J90', 'J91', 'J92', 'J93', 'J95', 'J96', 'J97', 'J98', 'J99', 'JE', 'JK', 'JM',
105
+ 'JNT', 'JOU', 'JPS', 'JWL', 'K1', 'K10', 'K11', 'K12', 'K13', 'K14', 'K15', 'K16', 'K17', 'K18',
106
+ 'K19', 'K2', 'K20', 'K21', 'K22', 'K23', 'K26', 'K27', 'K28', 'K3', 'K30', 'K31', 'K32', 'K33',
107
+ 'K34', 'K35', 'K36', 'K37', 'K38', 'K39', 'K40', 'K41', 'K42', 'K43', 'K45', 'K46', 'K47', 'K48',
108
+ 'K49', 'K50', 'K51', 'K52', 'K53', 'K54', 'K55', 'K58', 'K59', 'K6', 'K60', 'K61', 'K62', 'K63',
109
+ 'K64', 'K65', 'K66', 'K67', 'K68', 'K69', 'K70', 'K71', 'K73', 'K74', 'K75', 'K76', 'K77', 'K78',
110
+ 'K79', 'K80', 'K81', 'K82', 'K83', 'K84', 'K85', 'K86', 'K87', 'K88', 'K89', 'K90', 'K91', 'K92',
111
+ 'K93', 'K94', 'K95', 'K96', 'K97', 'K98', 'K99', 'KA', 'KAT', 'KB', 'KBA', 'KCC', 'KDW', 'KEL',
112
+ 'KGM', 'KGS', 'KHY', 'KHZ', 'KI', 'KIC', 'KIP', 'KJ', 'KJO', 'KL', 'KLK', 'KLX', 'KMA', 'KMH',
113
+ 'KMK', 'KMQ', 'KMT', 'KNI', 'KNM', 'KNS', 'KNT', 'KO', 'KPA', 'KPH', 'KPO', 'KPP', 'KR', 'KSD',
114
+ 'KSH', 'KT', 'KTN', 'KUR', 'KVA', 'KVR', 'KVT', 'KW', 'KWH', 'KWN', 'KWO', 'KWS', 'KWT', 'KWY',
115
+ 'KX', 'L10', 'L11', 'L12', 'L13', 'L14', 'L15', 'L16', 'L17', 'L18', 'L19', 'L2', 'L20', 'L21',
116
+ 'L23', 'L24', 'L25', 'L26', 'L27', 'L28', 'L29', 'L30', 'L31', 'L32', 'L33', 'L34', 'L35', 'L36',
117
+ 'L37', 'L38', 'L39', 'L40', 'L41', 'L42', 'L43', 'L44', 'L45', 'L46', 'L47', 'L48', 'L49', 'L50',
118
+ 'L51', 'L52', 'L53', 'L54', 'L55', 'L56', 'L57', 'L58', 'L59', 'L60', 'L63', 'L64', 'L65', 'L66',
119
+ 'L67', 'L68', 'L69', 'L70', 'L71', 'L72', 'L73', 'L74', 'L75', 'L76', 'L77', 'L78', 'L79', 'L80',
120
+ 'L81', 'L82', 'L83', 'L84', 'L85', 'L86', 'L87', 'L88', 'L89', 'L90', 'L91', 'L92', 'L93', 'L94',
121
+ 'L95', 'L96', 'L98', 'L99', 'LA', 'LAC', 'LBR', 'LBT', 'LD', 'LEF', 'LF', 'LH', 'LK', 'LM',
122
+ 'LN', 'LO', 'LP', 'LPA', 'LR', 'LS', 'LTN', 'LTR', 'LUB', 'LUM', 'LUX', 'LY', 'M1', 'M10',
123
+ 'M11', 'M12', 'M13', 'M14', 'M15', 'M16', 'M17', 'M18', 'M19', 'M20', 'M21', 'M22', 'M23', 'M24',
124
+ 'M25', 'M26', 'M27', 'M29', 'M30', 'M31', 'M32', 'M33', 'M34', 'M35', 'M36', 'M37', 'M38', 'M39',
125
+ 'M4', 'M40', 'M41', 'M42', 'M43', 'M44', 'M45', 'M46', 'M47', 'M48', 'M49', 'M5', 'M50', 'M51',
126
+ 'M52', 'M53', 'M55', 'M56', 'M57', 'M58', 'M59', 'M60', 'M61', 'M62', 'M63', 'M64', 'M65', 'M66',
127
+ 'M67', 'M68', 'M69', 'M7', 'M70', 'M71', 'M72', 'M73', 'M74', 'M75', 'M76', 'M77', 'M78', 'M79',
128
+ 'M80', 'M81', 'M82', 'M83', 'M84', 'M85', 'M86', 'M87', 'M88', 'M89', 'M9', 'M90', 'M91', 'M92',
129
+ 'M93', 'M94', 'M95', 'M96', 'M97', 'M98', 'M99', 'MAH', 'MAL', 'MAM', 'MAR', 'MAW', 'MBE', 'MBF',
130
+ 'MBR', 'MC', 'MCU', 'MD', 'MGM', 'MHZ', 'MIK', 'MIL', 'MIN', 'MIO', 'MIU', 'MKD', 'MKM', 'MKW',
131
+ 'MLD', 'MLT', 'MMK', 'MMQ', 'MMT', 'MND', 'MNJ', 'MON', 'MPA', 'MQD', 'MQH', 'MQM', 'MQS', 'MQW',
132
+ 'MRD', 'MRM', 'MRW', 'MSK', 'MTK', 'MTQ', 'MTR', 'MTS', 'MTZ', 'MVA', 'MWH', 'N1', 'N10', 'N11',
133
+ 'N12', 'N13', 'N14', 'N15', 'N16', 'N17', 'N18', 'N19', 'N20', 'N21', 'N22', 'N23', 'N24', 'N25',
134
+ 'N26', 'N27', 'N28', 'N29', 'N3', 'N30', 'N31', 'N32', 'N33', 'N34', 'N35', 'N36', 'N37', 'N38',
135
+ 'N39', 'N40', 'N41', 'N42', 'N43', 'N44', 'N45', 'N46', 'N47', 'N48', 'N49', 'N50', 'N51', 'N52',
136
+ 'N53', 'N54', 'N55', 'N56', 'N57', 'N58', 'N59', 'N60', 'N61', 'N62', 'N63', 'N64', 'N65', 'N66',
137
+ 'N67', 'N68', 'N69', 'N70', 'N71', 'N72', 'N73', 'N74', 'N75', 'N76', 'N77', 'N78', 'N79', 'N80',
138
+ 'N81', 'N82', 'N83', 'N84', 'N85', 'N86', 'N87', 'N88', 'N89', 'N90', 'N91', 'N92', 'N93', 'N94',
139
+ 'N95', 'N96', 'N97', 'N98', 'N99', 'NA', 'NAR', 'NCL', 'NEW', 'NF', 'NIL', 'NIU', 'NL', 'NM3',
140
+ 'NMI', 'NMP', 'NPT', 'NT', 'NTU', 'NU', 'NX', 'OA', 'ODE', 'ODG', 'ODK', 'ODM', 'OHM', 'ON',
141
+ 'ONZ', 'OPM', 'OT', 'OZA', 'OZI', 'P1', 'P10', 'P11', 'P12', 'P13', 'P14', 'P15', 'P16', 'P17',
142
+ 'P18', 'P19', 'P2', 'P20', 'P21', 'P22', 'P23', 'P24', 'P25', 'P26', 'P27', 'P28', 'P29', 'P30',
143
+ 'P31', 'P32', 'P33', 'P34', 'P35', 'P36', 'P37', 'P38', 'P39', 'P40', 'P41', 'P42', 'P43', 'P44',
144
+ 'P45', 'P46', 'P47', 'P48', 'P49', 'P5', 'P50', 'P51', 'P52', 'P53', 'P54', 'P55', 'P56', 'P57',
145
+ 'P58', 'P59', 'P60', 'P61', 'P62', 'P63', 'P64', 'P65', 'P66', 'P67', 'P68', 'P69', 'P70', 'P71',
146
+ 'P72', 'P73', 'P74', 'P75', 'P76', 'P77', 'P78', 'P79', 'P80', 'P81', 'P82', 'P83', 'P84', 'P85',
147
+ 'P86', 'P87', 'P88', 'P89', 'P90', 'P91', 'P92', 'P93', 'P94', 'P95', 'P96', 'P97', 'P98', 'P99',
148
+ 'PAL', 'PD', 'PFL', 'PGL', 'PI', 'PLA', 'PO', 'PQ', 'PR', 'PS', 'PTD', 'PTI', 'PTL', 'PTN',
149
+ 'Q10', 'Q11', 'Q12', 'Q13', 'Q14', 'Q15', 'Q16', 'Q17', 'Q18', 'Q19', 'Q20', 'Q21', 'Q22', 'Q23',
150
+ 'Q24', 'Q25', 'Q26', 'Q27', 'Q28', 'Q29', 'Q3', 'Q30', 'Q31', 'Q32', 'Q33', 'Q34', 'Q35', 'Q36',
151
+ 'Q37', 'Q38', 'Q39', 'Q40', 'Q41', 'Q42', 'QA', 'QAN', 'QB', 'QR', 'QTD', 'QTI', 'QTL', 'QTR',
152
+ 'R1', 'R9', 'RH', 'RM', 'ROM', 'RP', 'RPM', 'RPS', 'RT', 'S3', 'S4', 'SAN', 'SCO', 'SCR',
153
+ 'SEC', 'SET', 'SG', 'SIE', 'SM3', 'SMI', 'SQ', 'SQR', 'SR', 'STC', 'STI', 'STK', 'STL', 'STN',
154
+ 'STW', 'SW', 'SX', 'SYR', 'T0', 'T3', 'TAH', 'TAN', 'TI', 'TIC', 'TIP', 'TKM', 'TMS', 'TNE',
155
+ 'TP', 'TPI', 'TPR', 'TQD', 'TRL', 'TST', 'TTS', 'U1', 'U2', 'UB', 'UC', 'VA', 'VLT', 'VP',
156
+ 'W2', 'WA', 'WB', 'WCD', 'WE', 'WEB', 'WEE', 'WG', 'WHR', 'WM', 'WSD', 'WTT', 'X1', 'YDK',
157
+ 'YDQ', 'YRD', 'Z11', 'Z9', 'ZP', 'ZZ', 'X1A', 'X1B', 'X1D', 'X1F', 'X1G', 'X1W', 'X2C', 'X3A',
158
+ 'X3H', 'X43', 'X44', 'X4A', 'X4B', 'X4C', 'X4D', 'X4F', 'X4G', 'X4H', 'X5H', 'X5L', 'X5M', 'X6H',
159
+ 'X6P', 'X7A', 'X7B', 'X8A', 'X8B', 'X8C', 'XAA', 'XAB', 'XAC', 'XAD', 'XAE', 'XAF', 'XAG', 'XAH',
160
+ 'XAI', 'XAJ', 'XAL', 'XAM', 'XAP', 'XAT', 'XAV', 'XB4', 'XBA', 'XBB', 'XBC', 'XBD', 'XBE', 'XBF',
161
+ 'XBG', 'XBH', 'XBI', 'XBJ', 'XBK', 'XBL', 'XBM', 'XBN', 'XBO', 'XBP', 'XBQ', 'XBR', 'XBS', 'XBT',
162
+ 'XBU', 'XBV', 'XBW', 'XBX', 'XBY', 'XBZ', 'XCA', 'XCB', 'XCC', 'XCD', 'XCE', 'XCF', 'XCG', 'XCH',
163
+ 'XCI', 'XCJ', 'XCK', 'XCL', 'XCM', 'XCN', 'XCO', 'XCP', 'XCQ', 'XCR', 'XCS', 'XCT', 'XCU', 'XCV',
164
+ 'XCW', 'XCX', 'XCY', 'XCZ', 'XDA', 'XDB', 'XDC', 'XDG', 'XDH', 'XDI', 'XDJ', 'XDK', 'XDL', 'XDM',
165
+ 'XDN', 'XDP', 'XDR', 'XDS', 'XDT', 'XDU', 'XDV', 'XDW', 'XDX', 'XDY', 'XEC', 'XED', 'XEE', 'XEF',
166
+ 'XEG', 'XEH', 'XEI', 'XEN', 'XFB', 'XFC', 'XFD', 'XFE', 'XFI', 'XFL', 'XFO', 'XFP', 'XFR', 'XFT',
167
+ 'XFW', 'XFX', 'XGB', 'XGI', 'XGL', 'XGR', 'XGU', 'XGY', 'XGZ', 'XHA', 'XHB', 'XHC', 'XHG', 'XHN',
168
+ 'XHR', 'XIA', 'XIB', 'XIC', 'XID', 'XIE', 'XIF', 'XIG', 'XIH', 'XIK', 'XIL', 'XIN', 'XIZ', 'XJB',
169
+ 'XJC', 'XJG', 'XJR', 'XJT', 'XJY', 'XKG', 'XKI', 'XLE', 'XLG', 'XLT', 'XLU', 'XLV', 'XLZ', 'XMA',
170
+ 'XMB', 'XMC', 'XME', 'XMR', 'XMS', 'XMT', 'XMW', 'XMX', 'XNA', 'XNE', 'XNF', 'XNG', 'XNS', 'XNT',
171
+ 'XNU', 'XNV', 'XO1', 'XO2', 'XO3', 'XO4', 'XO5', 'XO6', 'XO7', 'XO8', 'XO9', 'XOA', 'XOB', 'XOC',
172
+ 'XOD', 'XOE', 'XOF', 'XOG', 'XOH', 'XOI', 'XOJ', 'XOK', 'XOL', 'XOM', 'XON', 'XOP', 'XOQ', 'XOR',
173
+ 'XOS', 'XOT', 'XOU', 'XOV', 'XOW', 'XOX', 'XOY', 'XOZ', 'XP1', 'XP2', 'XP3', 'XP4', 'XPA', 'XPB',
174
+ 'XPC', 'XPD', 'XPE', 'XPF', 'XPG', 'XPH', 'XPI', 'XPJ', 'XPK', 'XPL', 'XPN', 'XPO', 'XPP', 'XPR',
175
+ 'XPT', 'XPU', 'XPV', 'XPX', 'XPY', 'XPZ', 'XQA', 'XQB', 'XQC', 'XQD', 'XQF', 'XQG', 'XQH', 'XQJ',
176
+ 'XQK', 'XQL', 'XQM', 'XQN', 'XQP', 'XQQ', 'XQR', 'XQS', 'XRD', 'XRG', 'XRJ', 'XRK', 'XRL', 'XRO',
177
+ 'XRT', 'XRZ', 'XSA', 'XSB', 'XSC', 'XSD', 'XSE', 'XSH', 'XSI', 'XSK', 'XSL', 'XSM', 'XSO', 'XSP',
178
+ 'XSS', 'XST', 'XSU', 'XSV', 'XSW', 'XSX', 'XSY', 'XSZ', 'XT1', 'XTB', 'XTC', 'XTD', 'XTE', 'XTG',
179
+ 'XTI', 'XTK', 'XTL', 'XTN', 'XTO', 'XTR', 'XTS', 'XTT', 'XTU', 'XTV', 'XTW', 'XTY', 'XTZ', 'XUC',
180
+ 'XUN', 'XVA', 'XVG', 'XVI', 'XVK', 'XVL', 'XVN', 'XVO', 'XVP', 'XVQ', 'XVR', 'XVS', 'XVY', 'XWA',
181
+ 'XWB', 'XWC', 'XWD', 'XWF', 'XWG', 'XWH', 'XWJ', 'XWK', 'XWL', 'XWM', 'XWN', 'XWP', 'XWQ', 'XWR',
182
+ 'XWS', 'XWT', 'XWU', 'XWV', 'XWW', 'XWX', 'XWY', 'XWZ', 'XXA', 'XXB', 'XXC', 'XXD', 'XXF', 'XXG',
183
+ 'XXH', 'XXJ', 'XXK', 'XYA', 'XYB', 'XYC', 'XYD', 'XYF', 'XYG', 'XYH', 'XYJ', 'XYK', 'XYL', 'XYM',
184
+ 'XYN', 'XYP', 'XYQ', 'XYR', 'XYS', 'XYT', 'XYV', 'XYW', 'XYX', 'XYY', 'XYZ', 'XZA', 'XZB', 'XZC',
185
+ 'XZD', 'XZF', 'XZG', 'XZH', 'XZJ', 'XZK', 'XZL', 'XZM', 'XZN', 'XZP', 'XZQ', 'XZR', 'XZS', 'XZT',
186
+ 'XZU', 'XZV', 'XZW', 'XZX', 'XZY', 'XZZ',
187
+ ]);
188
+
189
+ /** the set every check uses; it never leaves this module */
190
+ const unitCodes: ReadonlySet<string> = new Set(BR_CL_23_CODES);
191
+
192
+ /** The EN 16931 unit code list (BR-CL-23) with its source. */
193
+ export const en16931UnitCodeList: IEn16931UnitCodeList = Object.freeze({
194
+ rule: 'BR-CL-23',
195
+ businessTerms: Object.freeze(['BT-130', 'BT-150'] as const),
196
+ codeLists: 'UN/ECE Recommendation 20 with Recommendation 21 extension',
197
+ source: 'EN 16931 validation artefacts (CEN/TC 434) validation-1.3.16',
198
+ codes: BR_CL_23_CODES,
199
+ });
200
+
201
+ /**
202
+ * Whether a value is a unit code EN 16931 admits (BR-CL-23). The comparison is exact: `HUR` is
203
+ * a code, `hur`, `HOURS` and ` HUR` are not.
204
+ */
205
+ export const isEn16931UnitCode = (value: unknown): value is string =>
206
+ typeof value === 'string' && unitCodes.has(value);
207
+
208
+ /**
209
+ * BR-CL-23 as the official rule tests a `unitCode` attribute: XML whitespace around the value is
210
+ * ignored, and a value with whitespace inside it, or an empty value, fails.
211
+ */
212
+ export const meetsUnitCodeRule = (attributeValue: string): boolean => {
213
+ const normalized = attributeValue.replace(/[ \t\r\n]+/g, ' ').replace(/^ | $/g, '');
214
+ return !normalized.includes(' ') && unitCodes.has(normalized);
215
+ };
@@ -4,6 +4,7 @@ import type { TAccountingDocItem } from '@tsclass/tsclass/dist_ts/finance/index.
4
4
  import type { EInvoice } from '../../einvoice.js';
5
5
  import type { IExtendedAccountingDocItem } from '../../interfaces/en16931-metadata.js';
6
6
  import { getDocumentTypeCode, isAccountingDocType } from '../utils/document.typecode.js';
7
+ import { en16931UnitCodeList, meetsUnitCodeRule } from '../utils/unit.codes.js';
7
8
 
8
9
  /**
9
10
  * Code List Validator for EN16931 compliance
@@ -208,33 +209,21 @@ export class CodeListValidator {
208
209
  }
209
210
 
210
211
  /**
211
- * Validate unit codes (UNECE Rec 20)
212
+ * Validate unit codes (BR-CL-23: UN/ECE Rec 20 with the Rec 21 extension)
212
213
  */
213
214
  private validateUnitCodes(invoice: EInvoice): void {
214
215
  invoice.items?.forEach((item, index) => {
215
216
  const unitCode = item.unitType;
216
- if (unitCode && !CodeLists.UNECERec20.codes.has(unitCode)) {
217
+ // an empty unit is an absent one, which BR-23 reports; BR-CL-23 checks a unit that is there
218
+ if (unitCode && !meetsUnitCodeRule(unitCode)) {
217
219
  this.addError(
218
220
  'BR-CL-23',
219
- `Invalid unit code: ${unitCode}. Must be UNECE Rec 20`,
221
+ `Invalid unit code: ${unitCode}. Must be UN/ECE Recommendation 20 with Rec 21 extension`,
220
222
  'EN16931',
221
- `items[${index}].unitCode`,
223
+ `items[${index}].unitType`,
222
224
  'BT-130',
223
225
  unitCode,
224
- 'Common codes: C62 (one), KGM (kilogram), HUR (hour), DAY (day), MTR (metre)'
225
- );
226
- }
227
-
228
- // Validate quantity is positive for standard units
229
- if (unitCode && item.unitQuantity <= 0 && unitCode !== 'LS') { // LS = Lump sum can be 1
230
- this.addError(
231
- 'BR-25',
232
- `Quantity must be positive for unit ${unitCode}`,
233
- 'EN16931',
234
- `items[${index}].quantity`,
235
- 'BT-129',
236
- item.unitQuantity,
237
- '> 0'
226
+ 'A code of the EN 16931 unit code list, for example C62 (one), H87 (piece), HUR (hour), DAY (day), MON (month), LS (lump sum)'
238
227
  );
239
228
  }
240
229
  });
@@ -310,7 +299,7 @@ export class CodeListValidator {
310
299
  return { name: 'UNCL4461', version: CodeLists.UNCL4461.version };
311
300
  }
312
301
  if (ruleId.includes('CL-23')) {
313
- return { name: 'UNECERec20', version: CodeLists.UNECERec20.version };
302
+ return { name: en16931UnitCodeList.codeLists, version: en16931UnitCodeList.source };
314
303
  }
315
304
  return undefined;
316
305
  }
@@ -381,56 +381,56 @@ export class EN16931BusinessRulesValidator {
381
381
  );
382
382
  }
383
383
 
384
- // BR-22: Each Invoice line shall have an Item name
384
+ // BR-25: Each Invoice line shall contain the Item name
385
385
  if (!item.name) {
386
386
  this.addError(
387
- 'BR-22',
387
+ 'BR-25',
388
388
  `Invoice line ${index + 1} must have an item name`,
389
389
  `items[${index}].name`
390
390
  );
391
391
  }
392
392
 
393
- // BR-23: An Invoice line shall have an Invoiced quantity
393
+ // BR-22: Each Invoice line shall have an Invoiced quantity
394
394
  if (item.unitQuantity === undefined || item.unitQuantity === null) {
395
395
  this.addError(
396
- 'BR-23',
396
+ 'BR-22',
397
397
  `Invoice line ${index + 1} must have a quantity`,
398
398
  `items[${index}].quantity`
399
399
  );
400
400
  }
401
401
 
402
- // BR-24: An Invoice line shall have an Invoiced quantity unit of measure code
402
+ // BR-23: An Invoice line shall have an Invoiced quantity unit of measure code
403
403
  if (!item.unitType) {
404
404
  this.addError(
405
- 'BR-24',
405
+ 'BR-23',
406
406
  `Invoice line ${index + 1} must have a unit of measure code`,
407
- `items[${index}].unitCode`
407
+ `items[${index}].unitType`
408
408
  );
409
409
  }
410
410
 
411
- // BR-25: An Invoice line shall have an Invoice line net amount
411
+ // BR-24: Each Invoice line shall have an Invoice line net amount
412
412
  const lineNetAmount = item.unitNetPrice * item.unitQuantity;
413
413
  if (isNaN(lineNetAmount)) {
414
414
  this.addError(
415
- 'BR-25',
415
+ 'BR-24',
416
416
  `Invoice line ${index + 1} must have a valid net amount`,
417
417
  `items[${index}]`
418
418
  );
419
419
  }
420
420
 
421
- // BR-26: Each Invoice line shall have an Invoice line VAT category code
421
+ // BR-CO-04: Each Invoice line shall be categorized with an Invoiced item VAT category code
422
422
  if (item.vatPercentage === undefined) {
423
423
  this.addError(
424
- 'BR-26',
424
+ 'BR-CO-04',
425
425
  `Invoice line ${index + 1} must have a VAT category code`,
426
426
  `items[${index}].vatPercentage`
427
427
  );
428
428
  }
429
429
 
430
- // BR-27: Invoice line net price shall be present
430
+ // BR-26: Each Invoice line shall contain the Item net price
431
431
  if (item.unitNetPrice === undefined || item.unitNetPrice === null) {
432
432
  this.addError(
433
- 'BR-27',
433
+ 'BR-26',
434
434
  `Invoice line ${index + 1} must have a net price`,
435
435
  `items[${index}].unitPrice`
436
436
  );