@fin.cx/einvoice 8.0.0 → 8.1.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 (55) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/einvoice.js +32 -2
  3. package/dist_ts/formats/base/base.validator.d.ts +5 -0
  4. package/dist_ts/formats/base/base.validator.js +28 -1
  5. package/dist_ts/formats/cii/cii.validator.d.ts +11 -0
  6. package/dist_ts/formats/cii/cii.validator.js +35 -1
  7. package/dist_ts/formats/cii/facturx/facturx.decoder.js +3 -2
  8. package/dist_ts/formats/cii/facturx/facturx.encoder.js +5 -2
  9. package/dist_ts/formats/cii/facturx/facturx.validator.js +8 -4
  10. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +3 -2
  11. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +5 -2
  12. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +3 -2
  13. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +5 -4
  14. package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +5 -0
  15. package/dist_ts/formats/converters/xml-to-einvoice.converter.js +11 -2
  16. package/dist_ts/formats/semantic/semantic.adapter.js +2 -2
  17. package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -1
  18. package/dist_ts/formats/ubl/generic/ubl.encoder.js +5 -2
  19. package/dist_ts/formats/ubl/ubl.validator.d.ts +11 -0
  20. package/dist_ts/formats/ubl/ubl.validator.js +35 -1
  21. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +3 -2
  22. package/dist_ts/formats/utils/unit.codes.d.ts +40 -0
  23. package/dist_ts/formats/utils/unit.codes.js +193 -0
  24. package/dist_ts/formats/validation/codelist.validator.d.ts +1 -1
  25. package/dist_ts/formats/validation/codelist.validator.js +7 -9
  26. package/dist_ts/formats/validation/en16931.business-rules.validator.js +13 -13
  27. package/dist_ts/formats/validation/validation.types.d.ts +0 -4
  28. package/dist_ts/formats/validation/validation.types.js +1 -28
  29. package/dist_ts/index.d.ts +4 -0
  30. package/dist_ts/index.js +6 -1
  31. package/dist_ts_install/download-schematron.js +3 -5
  32. package/package.json +7 -7
  33. package/readme.md +44 -1
  34. package/ts/00_commitinfo_data.ts +1 -1
  35. package/ts/einvoice.ts +33 -1
  36. package/ts/formats/base/base.validator.ts +28 -0
  37. package/ts/formats/cii/cii.validator.ts +47 -0
  38. package/ts/formats/cii/facturx/facturx.decoder.ts +2 -1
  39. package/ts/formats/cii/facturx/facturx.encoder.ts +4 -1
  40. package/ts/formats/cii/facturx/facturx.validator.ts +9 -3
  41. package/ts/formats/cii/zugferd/zugferd.decoder.ts +2 -1
  42. package/ts/formats/cii/zugferd/zugferd.encoder.ts +4 -1
  43. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +2 -1
  44. package/ts/formats/cii/zugferd/zugferd.validator.ts +4 -3
  45. package/ts/formats/converters/xml-to-einvoice.converter.ts +11 -1
  46. package/ts/formats/semantic/semantic.adapter.ts +1 -1
  47. package/ts/formats/ubl/en16931.ubl.validator.ts +6 -0
  48. package/ts/formats/ubl/generic/ubl.encoder.ts +4 -1
  49. package/ts/formats/ubl/ubl.validator.ts +47 -0
  50. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +2 -1
  51. package/ts/formats/utils/unit.codes.ts +215 -0
  52. package/ts/formats/validation/codelist.validator.ts +8 -19
  53. package/ts/formats/validation/en16931.business-rules.validator.ts +13 -13
  54. package/ts/formats/validation/validation.types.ts +0 -28
  55. package/ts/index.ts +9 -0
@@ -62,33 +62,6 @@ export const CodeLists = {
62
62
  ['58', 'SEPA credit transfer'],
63
63
  ['59', 'SEPA direct debit']
64
64
  ])
65
- },
66
- // UNECE Rec 20 Unit codes (subset)
67
- UNECERec20: {
68
- version: '2021',
69
- codes: new Map([
70
- ['C62', 'One (unit)'],
71
- ['DAY', 'Day'],
72
- ['HAR', 'Hectare'],
73
- ['HUR', 'Hour'],
74
- ['KGM', 'Kilogram'],
75
- ['KTM', 'Kilometre'],
76
- ['KWH', 'Kilowatt hour'],
77
- ['LS', 'Lump sum'],
78
- ['LTR', 'Litre'],
79
- ['MIN', 'Minute'],
80
- ['MMT', 'Millimetre'],
81
- ['MON', 'Month'],
82
- ['MTK', 'Square metre'],
83
- ['MTQ', 'Cubic metre'],
84
- ['MTR', 'Metre'],
85
- ['NAR', 'Number of articles'],
86
- ['NPR', 'Number of pairs'],
87
- ['P1', 'Percent'],
88
- ['SET', 'Set'],
89
- ['TNE', 'Tonne (metric ton)'],
90
- ['WEE', 'Week']
91
- ])
92
65
  }
93
66
  };
94
67
  // Business Term (BT) and Business Group (BG) mappings
@@ -178,4 +151,4 @@ export const SemanticModel = {
178
151
  BG31: 'Item information',
179
152
  BG32: 'Item attributes'
180
153
  };
181
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi50eXBlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2Zvcm1hdHMvdmFsaWRhdGlvbi92YWxpZGF0aW9uLnR5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBcUZILHdCQUF3QjtBQUN4QixNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUc7SUFDdkIsMEJBQTBCO0lBQzFCLE9BQU8sRUFBRTtRQUNQLE9BQU8sRUFBRSxNQUFNO1FBQ2YsS0FBSyxFQUFFLElBQUksR0FBRyxDQUFDO1lBQ2IsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSztZQUNwRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLO1NBQ3JFLENBQUM7S0FDSDtJQUVELG1DQUFtQztJQUNuQyxPQUFPLEVBQUU7UUFDUCxPQUFPLEVBQUUsTUFBTTtRQUNmLEtBQUssRUFBRSxJQUFJLEdBQUcsQ0FBQztZQUNiLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSTtZQUN0RSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUk7WUFDdEUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFB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
154
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi50eXBlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2Zvcm1hdHMvdmFsaWRhdGlvbi92YWxpZGF0aW9uLnR5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBcUZILHdCQUF3QjtBQUN4QixNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUc7SUFDdkIsMEJBQTBCO0lBQzFCLE9BQU8sRUFBRTtRQUNQLE9BQU8sRUFBRSxNQUFNO1FBQ2YsS0FBSyxFQUFFLElBQUksR0FBRyxDQUFDO1lBQ2IsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSztZQUNwRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLO1NBQ3JFLENBQUM7S0FDSDtJQUVELG1DQUFtQztJQUNuQyxPQUFPLEVBQUU7UUFDUCxPQUFPLEVBQUUsTUFBTTtRQUNmLEtBQUssRUFBRSxJQUFJLEdBQUcsQ0FBQztZQUNiLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSTtZQUN0RSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUk7WUFDdEUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJO1NBQ3ZFLENBQUM7S0FDSDtJQUVELDhCQUE4QjtJQUM5QixRQUFRLEVBQUU7UUFDUixPQUFPLEVBQUUsTUFBTTtRQUNmLEtBQUssRUFBRSxJQUFJLEdBQUcsQ0FBQztZQUNiLENBQUMsR0FBRyxFQUFFLGVBQWUsQ0FBQztZQUN0QixDQUFDLEdBQUcsRUFBRSxZQUFZLENBQUM7WUFDbkIsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLENBQUM7WUFDeEIsQ0FBQyxJQUFJLEVBQUUsb0JBQW9CLENBQUM7WUFDNUIsQ0FBQyxHQUFHLEVBQUUsMkNBQTJDLENBQUM7WUFDbEQsQ0FBQyxHQUFHLEVBQUUsd0JBQXdCLENBQUM7WUFDL0IsQ0FBQyxHQUFHLEVBQUUsK0JBQStCLENBQUM7WUFDdEMsQ0FBQyxHQUFHLEVBQUUscUNBQXFDLENBQUM7WUFDNUMsQ0FBQyxHQUFHLEVBQUUsbUVBQW1FLENBQUM7U0FDM0UsQ0FBQztLQUNIO0lBRUQsK0JBQStCO0lBQy9CLFFBQVEsRUFBRTtRQUNSLE9BQU8sRUFBRSxNQUFNO1FBQ2YsS0FBSyxFQUFFLElBQUksR0FBRyxDQUFDO1lBQ2IsQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLENBQUM7WUFDN0IsQ0FBQyxLQUFLLEVBQUUsYUFBYSxDQUFDO1lBQ3RCLENBQUMsS0FBSyxFQUFFLFlBQVksQ0FBQztZQUNyQixDQUFDLEtBQUssRUFBRSxtQkFBbUIsQ0FBQztZQUM1QixDQUFDLEtBQUssRUFBRSxxQkFBcUIsQ0FBQztZQUM5QixDQUFDLEtBQUssRUFBRSw2Q0FBNkMsQ0FBQztTQUN2RCxDQUFDO0tBQ0g7SUFFRCwrQkFBK0I7SUFDL0IsUUFBUSxFQUFFO1FBQ1IsT0FBTyxFQUFFLE1BQU07UUFDZixLQUFLLEVBQUUsSUFBSSxHQUFHLENBQUM7WUFDYixDQUFDLEdBQUcsRUFBRSx3QkFBd0IsQ0FBQztZQUMvQixDQUFDLElBQUksRUFBRSxTQUFTLENBQUM7WUFDakIsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDO1lBQ2hCLENBQUMsSUFBSSxFQUFFLGlCQUFpQixDQUFDO1lBQ3pCLENBQUMsSUFBSSxFQUFFLGdCQUFnQixDQUFDO1lBQ3hCLENBQUMsSUFBSSxFQUFFLHlCQUF5QixDQUFDO1lBQ2pDLENBQUMsSUFBSSxFQUFFLFdBQVcsQ0FBQztZQUNuQixDQUFDLElBQUksRUFBRSxjQUFjLENBQUM7WUFDdEIsQ0FBQyxJQUFJLEVBQUUsc0JBQXNCLENBQUM7WUFDOUIsQ0FBQyxJQUFJLEVBQUUsbUJBQW1CLENBQUM7U0FDNUIsQ0FBQztLQUNIO0NBQ0YsQ0FBQztBQUVGLHNEQUFzRDtBQUN0RCxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUc7SUFDM0IscUJBQXFCO0lBQ3JCLEdBQUcsRUFBRSxnQkFBZ0I7SUFDckIsR0FBRyxFQUFFLG9CQUFvQjtJQUN6QixHQUFHLEVBQUUsbUJBQW1CO0lBQ3hCLEdBQUcsRUFBRSx1QkFBdUI7SUFDNUIsR0FBRyxFQUFFLDhCQUE4QjtJQUNuQyxHQUFHLEVBQUUsNEJBQTRCO0lBQ2pDLEdBQUcsRUFBRSxpQ0FBaUM7SUFDdEMsR0FBRyxFQUFFLGtCQUFrQjtJQUN2QixJQUFJLEVBQUUsaUJBQWlCO0lBQ3ZCLElBQUksRUFBRSxtQkFBbUI7SUFDekIsSUFBSSxFQUFFLG9CQUFvQjtJQUMxQixJQUFJLEVBQUUsMEJBQTBCO0lBQ2hDLElBQUksRUFBRSx1QkFBdUI7SUFDN0IsSUFBSSxFQUFFLDRCQUE0QjtJQUNsQyxJQUFJLEVBQUUsMkJBQTJCO0lBQ2pDLElBQUksRUFBRSx5QkFBeUI7SUFDL0IsSUFBSSxFQUFFLDRCQUE0QjtJQUNsQyxJQUFJLEVBQUUsNEJBQTRCO0lBQ2xDLElBQUksRUFBRSxlQUFlO0lBQ3JCLElBQUksRUFBRSxhQUFhO0lBQ25CLElBQUksRUFBRSxZQUFZO0lBQ2xCLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsSUFBSSxFQUFFLDBCQUEwQjtJQUVoQyxvQkFBb0I7SUFDcEIsSUFBSSxFQUFFLGFBQWE7SUFDbkIsSUFBSSxFQUFFLHFCQUFxQjtJQUMzQixJQUFJLEVBQUUsbUJBQW1CO0lBQ3pCLElBQUksRUFBRSxzQ0FBc0M7SUFDNUMsSUFBSSxFQUFFLHVCQUF1QjtJQUM3QixJQUFJLEVBQUUsb0NBQW9DO0lBQzFDLElBQUksRUFBRSxxQ0FBcUM7SUFDM0MsSUFBSSxFQUFFLDJCQUEyQjtJQUVqQyxtQkFBbUI7SUFDbkIsSUFBSSxFQUFFLFlBQVk7SUFDbEIsSUFBSSxFQUFFLG9CQUFvQjtJQUMxQixJQUFJLEVBQUUsa0JBQWtCO0lBQ3hCLElBQUksRUFBRSxxQ0FBcUM7SUFDM0MsSUFBSSxFQUFFLHNCQUFzQjtJQUM1QixJQUFJLEVBQUUsMEJBQTBCO0lBRWhDLDBCQUEwQjtJQUMxQixLQUFLLEVBQUUsZ0NBQWdDO0lBQ3ZDLEtBQUssRUFBRSxxQ0FBcUM7SUFDNUMsS0FBSyxFQUFFLGtDQUFrQztJQUN6QyxLQUFLLEVBQUUsa0NBQWtDO0lBQ3pDLEtBQUssRUFBRSwwQkFBMEI7SUFDakMsS0FBSyxFQUFFLGlEQUFpRDtJQUN4RCxLQUFLLEVBQUUsK0JBQStCO0lBQ3RDLEtBQUssRUFBRSxhQUFhO0lBQ3BCLEtBQUssRUFBRSxpQkFBaUI7SUFDeEIsS0FBSyxFQUFFLHdCQUF3QjtJQUUvQixrQkFBa0I7SUFDbEIsR0FBRyxFQUFFLGNBQWM7SUFDbkIsR0FBRyxFQUFFLGlCQUFpQjtJQUN0QixHQUFHLEVBQUUsNkJBQTZCO0lBQ2xDLEdBQUcsRUFBRSxRQUFRO0lBQ2IsR0FBRyxFQUFFLHVCQUF1QjtJQUM1QixHQUFHLEVBQUUsZ0JBQWdCO0lBQ3JCLEdBQUcsRUFBRSxPQUFPO0lBQ1osR0FBRyxFQUFFLHNCQUFzQjtJQUMzQixHQUFHLEVBQUUsZUFBZTtJQUNwQixJQUFJLEVBQUUsT0FBTztJQUNiLElBQUksRUFBRSwyQkFBMkI7SUFDakMsSUFBSSxFQUFFLDBDQUEwQztJQUNoRCxJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLElBQUksRUFBRSw0QkFBNEI7SUFDbEMsSUFBSSxFQUFFLG9CQUFvQjtJQUMxQixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLElBQUksRUFBRSxpQkFBaUI7SUFDdkIsSUFBSSxFQUFFLDBCQUEwQjtJQUNoQyxJQUFJLEVBQUUsY0FBYztJQUNwQixJQUFJLEVBQUUsMkJBQTJCO0lBQ2pDLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsSUFBSSxFQUFFLGlCQUFpQjtJQUN2QixJQUFJLEVBQUUsZUFBZTtJQUNyQixJQUFJLEVBQUUsaUNBQWlDO0lBQ3ZDLElBQUksRUFBRSxjQUFjO0lBQ3BCLElBQUksRUFBRSxxQkFBcUI7SUFDM0IsSUFBSSxFQUFFLHlCQUF5QjtJQUMvQixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLElBQUksRUFBRSxlQUFlO0lBQ3JCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsSUFBSSxFQUFFLGtCQUFrQjtJQUN4QixJQUFJLEVBQUUsaUJBQWlCO0NBQ3hCLENBQUMifQ==
@@ -17,6 +17,8 @@ import { PDFExtractor } from './formats/pdf/pdf.extractor.js';
17
17
  import { BaseXMLExtractor, StandardXMLExtractor, AssociatedFilesExtractor, TextXMLExtractor } from './formats/pdf/extractors/index.js';
18
18
  import { FormatDetector } from './formats/utils/format.detector.js';
19
19
  import { getAccountingDocType, getDocumentTypeCode, isAccountingDocType } from './formats/utils/document.typecode.js';
20
+ import { en16931UnitCodeList, isEn16931UnitCode } from './formats/utils/unit.codes.js';
21
+ import type { IEn16931UnitCodeList } from './formats/utils/unit.codes.js';
20
22
  import { EInvoiceError, EInvoiceParsingError, EInvoiceValidationError, EInvoicePDFError, EInvoiceFormatError, ErrorRecovery, ErrorContext } from './errors.js';
21
23
  import { FacturXDecoder } from './formats/cii/facturx/facturx.decoder.js';
22
24
  import { FacturXEncoder } from './formats/cii/facturx/facturx.encoder.js';
@@ -38,6 +40,8 @@ export { ZUGFeRDDecoder, ZUGFeRDEncoder, ZUGFeRDValidator, ZUGFeRDV1Decoder };
38
40
  export { PDFEmbedder, PDFExtractor, BaseXMLExtractor, StandardXMLExtractor, AssociatedFilesExtractor, TextXMLExtractor };
39
41
  export { FormatDetector };
40
42
  export { getAccountingDocType, getDocumentTypeCode, isAccountingDocType };
43
+ export { en16931UnitCodeList, isEn16931UnitCode };
44
+ export type { IEn16931UnitCodeList };
41
45
  export { EInvoiceError, EInvoiceParsingError, EInvoiceValidationError, EInvoicePDFError, EInvoiceFormatError, ErrorRecovery, ErrorContext };
42
46
  /**
43
47
  * Validates an XML string against the appropriate format rules
package/dist_ts/index.js CHANGED
@@ -26,6 +26,8 @@ import { BaseXMLExtractor, StandardXMLExtractor, AssociatedFilesExtractor, TextX
26
26
  import { FormatDetector } from './formats/utils/format.detector.js';
27
27
  // Import the document type code table (BT-3)
28
28
  import { getAccountingDocType, getDocumentTypeCode, isAccountingDocType } from './formats/utils/document.typecode.js';
29
+ // Import the EN 16931 unit code list (BR-CL-23)
30
+ import { en16931UnitCodeList, isEn16931UnitCode } from './formats/utils/unit.codes.js';
29
31
  // Import error classes
30
32
  import { EInvoiceError, EInvoiceParsingError, EInvoiceValidationError, EInvoicePDFError, EInvoiceFormatError, ErrorRecovery, ErrorContext } from './errors.js';
31
33
  // Import Factur-X implementation
@@ -61,6 +63,9 @@ export { FormatDetector };
61
63
  // Export the document type code table, so a consumer that reads or writes BT-3
62
64
  // itself does not need a second copy of it
63
65
  export { getAccountingDocType, getDocumentTypeCode, isAccountingDocType };
66
+ // Export the EN 16931 unit code list (BR-CL-23: UN/ECE Recommendation 20 with the
67
+ // Recommendation 21 extension), so a consumer can check a unit before it builds a document
68
+ export { en16931UnitCodeList, isEn16931UnitCode };
64
69
  // Export error classes
65
70
  export { EInvoiceError, EInvoiceParsingError, EInvoiceValidationError, EInvoicePDFError, EInvoiceFormatError, ErrorRecovery, ErrorContext };
66
71
  /**
@@ -93,4 +98,4 @@ export function validateXml(xml, level = common.ValidationLevel.SYNTAX) {
93
98
  export function createEInvoice() {
94
99
  return new EInvoice();
95
100
  }
96
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxvQkFBb0I7QUFDcEIsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUV6QyxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLE1BQU0sTUFBTSx3QkFBd0IsQ0FBQztBQUVqRCxtQkFBbUI7QUFDbkIsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHdDQUF3QyxDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQztBQUN4RSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwwQ0FBMEMsQ0FBQztBQUU1RSxzQkFBc0I7QUFDdEIsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQzdELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFFakUsMEJBQTBCO0FBQzFCLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDOUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFbEUsMEJBQTBCO0FBQzFCLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDOUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFbEUsdUJBQXVCO0FBQ3ZCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUM1RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDOUQsT0FBTyxFQUNMLGdCQUFnQixFQUNoQixvQkFBb0IsRUFDcEIsd0JBQXdCLEVBQ3hCLGdCQUFnQixFQUNqQixNQUFNLG1DQUFtQyxDQUFDO0FBRTNDLHlCQUF5QjtBQUN6QixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFFcEUsNkNBQTZDO0FBQzdDLE9BQU8sRUFDTCxvQkFBb0IsRUFDcEIsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNwQixNQUFNLHNDQUFzQyxDQUFDO0FBRTlDLHVCQUF1QjtBQUN2QixPQUFPLEVBQ0wsYUFBYSxFQUNiLG9CQUFvQixFQUNwQix1QkFBdUIsRUFDdkIsZ0JBQWdCLEVBQ2hCLG1CQUFtQixFQUNuQixhQUFhLEVBQ2IsWUFBWSxFQUNiLE1BQU0sYUFBYSxDQUFDO0FBRXJCLGlDQUFpQztBQUNqQyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMENBQTBDLENBQUM7QUFDMUUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDBDQUEwQyxDQUFDO0FBQzFFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDRDQUE0QyxDQUFDO0FBRTlFLGdDQUFnQztBQUNoQyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMENBQTBDLENBQUM7QUFDMUUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDBDQUEwQyxDQUFDO0FBQzFFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDRDQUE0QyxDQUFDO0FBQzlFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDZDQUE2QyxDQUFDO0FBNEIvRSxPQUFPLEVBQUUsZUFBZSxFQUFFLGFBQWEsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRXhFLHFDQUFxQztBQUNyQyxPQUFPLEVBQUUsTUFBTSxJQUFJLFVBQVUsRUFBRSxDQUFDO0FBRWhDLG9CQUFvQjtBQUNwQixPQUFPLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFFcEIsbUJBQW1CO0FBQ25CLE9BQU8sRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixFQUFFLENBQUM7QUFFNUQsc0JBQXNCO0FBQ3RCLE9BQU8sRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxDQUFDO0FBRW5ELDBCQUEwQjtBQUMxQixPQUFPLEVBQUUsY0FBYyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO0FBRTVELDBCQUEwQjtBQUMxQixPQUFPLEVBQUUsY0FBYyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO0FBRTVELGlDQUFpQztBQUNqQyxPQUFPLEVBQUUsY0FBYyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO0FBRTVELGdDQUFnQztBQUNoQyxPQUFPLEVBQUUsY0FBYyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO0FBRTlFLHVCQUF1QjtBQUN2QixPQUFPLEVBQ0wsV0FBVyxFQUNYLFlBQVksRUFDWixnQkFBZ0IsRUFDaEIsb0JBQW9CLEVBQ3BCLHdCQUF3QixFQUN4QixnQkFBZ0IsRUFDakIsQ0FBQztBQUVGLHlCQUF5QjtBQUN6QixPQUFPLEVBQUUsY0FBYyxFQUFFLENBQUM7QUFFMUIsK0VBQStFO0FBQy9FLDJDQUEyQztBQUMzQyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQztBQUUxRSx1QkFBdUI7QUFDdkIsT0FBTyxFQUNMLGFBQWEsRUFDYixvQkFBb0IsRUFDcEIsdUJBQXVCLEVBQ3ZCLGdCQUFnQixFQUNoQixtQkFBbUIsRUFDbkIsYUFBYSxFQUNiLFlBQVksRUFDYixDQUFDO0FBRUY7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUN6QixHQUFXLEVBQ1gsUUFBZ0MsTUFBTSxDQUFDLGVBQWUsQ0FBQyxNQUFNO0lBRTdELElBQUksQ0FBQztRQUNILE1BQU0sU0FBUyxHQUFHLGdCQUFnQixDQUFDLGVBQWUsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN4RCxPQUFPLFNBQVMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDbkMsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixNQUFNLFlBQVksR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDNUUsT0FBTztZQUNMLEtBQUssRUFBRSxLQUFLO1lBQ1osTUFBTSxFQUFFLENBQUM7b0JBQ1AsSUFBSSxFQUFFLFdBQVc7b0JBQ2pCLE9BQU8sRUFBRSxxQkFBcUIsWUFBWSxFQUFFO2lCQUM3QyxDQUFDO1lBQ0YsS0FBSztTQUNOLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxjQUFjO0lBQzVCLE9BQU8sSUFBSSxRQUFRLEVBQUUsQ0FBQztBQUN4QixDQUFDIn0=
101
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxvQkFBb0I7QUFDcEIsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUV6QyxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLE1BQU0sTUFBTSx3QkFBd0IsQ0FBQztBQUVqRCxtQkFBbUI7QUFDbkIsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHdDQUF3QyxDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQztBQUN4RSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwwQ0FBMEMsQ0FBQztBQUU1RSxzQkFBc0I7QUFDdEIsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQzdELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFFakUsMEJBQTBCO0FBQzFCLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDOUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFbEUsMEJBQTBCO0FBQzFCLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDOUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFbEUsdUJBQXVCO0FBQ3ZCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUM1RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDOUQsT0FBTyxFQUNMLGdCQUFnQixF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
@@ -60,10 +60,8 @@ async function main() {
60
60
  });
61
61
  console.log('\n✅ Schematron download complete!');
62
62
  }
63
- // Run if executed directly. tsrun, which the npm script uses, leaves process.argv
64
- // empty, so a plain comparison against argv[1] would never match and the script
65
- // would exit silently without downloading anything.
66
- const isEntryPoint = !process.argv[1] || import.meta.url === `file://${process.argv[1]}`;
63
+ // Run if executed directly
64
+ const isEntryPoint = import.meta.url === `file://${process.argv[1]}`;
67
65
  if (isEntryPoint) {
68
66
  main().catch(error => {
69
67
  console.error('❌ Script failed:', error);
@@ -71,4 +69,4 @@ if (isEntryPoint) {
71
69
  });
72
70
  }
73
71
  export default main;
74
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG93bmxvYWQtc2NoZW1hdHJvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2luc3RhbGwvZG93bmxvYWQtc2NoZW1hdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQ0EsOEJBQThCO0FBRTlCOztHQUVHO0FBRUgsTUFBTSw4QkFBOEIsR0FBRyx3REFBd0QsQ0FBQztBQUVoRyxLQUFLLFVBQVUsZ0JBQWdCO0lBQzdCLE1BQU0sRUFBRSxvQkFBb0IsRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFDOUUsTUFBTSxVQUFVLEdBQUcsSUFBSSxvQkFBb0IsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQzVFLE1BQU0sVUFBVSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQzlCLE9BQU8sVUFBVSxDQUFDO0FBQ3BCLENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNqQixPQUFPLENBQUMsR0FBRyxDQUFDLHNDQUFzQyxDQUFDLENBQUM7SUFFcEQsTUFBTSxVQUFVLEdBQUcsTUFBTSxnQkFBZ0IsRUFBRSxDQUFDO0lBRTVDLG9DQUFvQztJQUNwQyxPQUFPLENBQUMsR0FBRyxDQUFDLDRDQUE0QyxDQUFDLENBQUM7SUFDMUQsSUFBSSxDQUFDO1FBQ0gsTUFBTSxZQUFZLEdBQUcsTUFBTSxVQUFVLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDbEUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsWUFBWSxDQUFDLE1BQU0sZ0JBQWdCLENBQUMsQ0FBQztRQUNqRSxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBUyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxZQUFZLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FB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
72
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG93bmxvYWQtc2NoZW1hdHJvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2luc3RhbGwvZG93bmxvYWQtc2NoZW1hdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQ0EsOEJBQThCO0FBRTlCOztHQUVHO0FBRUgsTUFBTSw4QkFBOEIsR0FBRyx3REFBd0QsQ0FBQztBQUVoRyxLQUFLLFVBQVUsZ0JBQWdCO0lBQzdCLE1BQU0sRUFBRSxvQkFBb0IsRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFDOUUsTUFBTSxVQUFVLEdBQUcsSUFBSSxvQkFBb0IsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQzVFLE1BQU0sVUFBVSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQzlCLE9BQU8sVUFBVSxDQUFDO0FBQ3BCLENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNqQixPQUFPLENBQUMsR0FBRyxDQUFDLHNDQUFzQyxDQUFDLENBQUM7SUFFcEQsTUFBTSxVQUFVLEdBQUcsTUFBTSxnQkFBZ0IsRUFBRSxDQUFDO0lBRTVDLG9DQUFvQztJQUNwQyxPQUFPLENBQUMsR0FBRyxDQUFDLDRDQUE0QyxDQUFDLENBQUM7SUFDMUQsSUFBSSxDQUFDO1FBQ0gsTUFBTSxZQUFZLEdBQUcsTUFBTSxVQUFVLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDbEUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsWUFBWSxDQUFDLE1BQU0sZ0JBQWdCLENBQUMsQ0FBQztRQUNqRSxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBUyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxZQUFZLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fin.cx/einvoice",
3
- "version": "8.0.0",
3
+ "version": "8.1.0",
4
4
  "private": false,
5
5
  "description": "A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.",
6
6
  "main": "dist_ts/index.js",
@@ -13,12 +13,12 @@
13
13
  "author": "Task Venture Capital GmbH",
14
14
  "license": "MIT",
15
15
  "devDependencies": {
16
- "@git.zone/tsbuild": "^4.5.0",
17
- "@git.zone/tsbundle": "^2.13.0",
18
- "@git.zone/tsrun": "^2.0.6",
19
- "@git.zone/tstest": "^6.1.1",
16
+ "@git.zone/tsbuild": "^5.0.0",
17
+ "@git.zone/tsbundle": "^2.15.0",
18
+ "@git.zone/tsrun": "^3.0.0",
19
+ "@git.zone/tstest": "^6.2.0",
20
20
  "@push.rocks/smartfile": "^13.1.3",
21
- "@types/node": "^26.5.1"
21
+ "@types/node": "^26.6.1"
22
22
  },
23
23
  "dependencies": {
24
24
  "@push.rocks/smartxml": "^2.1.0",
@@ -67,7 +67,7 @@
67
67
  ],
68
68
  "scripts": {
69
69
  "test": "pnpm run test:unit",
70
- "test:unit": "tstest test/test.cii-xrechnung.node.ts --verbose --logfile --timeout 60 && tstest test/test.einvoice.ts --verbose --logfile --timeout 60 && tstest test/test.decimal.ts --verbose --logfile --timeout 60 && tstest test/test.decimal-currency-calculator.ts --verbose --logfile --timeout 60 && tstest test/test.currency-utils.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-validators.ts --verbose --logfile --timeout 60 && tstest test/test.schematron-validator.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-creditnote.node.ts --verbose --logfile --timeout 60 && tstest test/test.accountingdoc-types.node.ts --verbose --logfile --timeout 60",
70
+ "test:unit": "tstest test/test.cii-xrechnung.node.ts --verbose --logfile --timeout 60 && tstest test/test.einvoice.ts --verbose --logfile --timeout 60 && tstest test/test.decimal.ts --verbose --logfile --timeout 60 && tstest test/test.decimal-currency-calculator.ts --verbose --logfile --timeout 60 && tstest test/test.currency-utils.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-validators.ts --verbose --logfile --timeout 60 && tstest test/test.schematron-validator.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-creditnote.node.ts --verbose --logfile --timeout 60 && tstest test/test.accountingdoc-types.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-unitcodes.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-lineunit.node.ts --verbose --logfile --timeout 60",
71
71
  "test:format": "tstest test/test.format-detection.ts --verbose --logfile --timeout 60",
72
72
  "test:corpus": "tstest test/suite/einvoice_corpus-validation/ --verbose --logfile --timeout 60",
73
73
  "test:performance": "NODE_OPTIONS=--expose-gc tstest test/suite/einvoice_performance/ --verbose --logfile --timeout 60",
package/readme.md CHANGED
@@ -218,6 +218,48 @@ console.log(business.errors);
218
218
  console.log(business.warnings ?? []);
219
219
  ```
220
220
 
221
+ ### Unit codes (BR-CL-23)
222
+
223
+ A line's `unitType` is written as the unit code of the invoiced quantity (BT-130), so it has to
224
+ be a code EN 16931 admits: UN/ECE Recommendation 20 with the Recommendation 21 extension, as
225
+ business rule BR-CL-23 lists them. Words such as `hours` or `HOURS` are not codes; `HUR` (hour),
226
+ `DAY` (day), `MON` (month), `H87` (piece), `C62` (one), `LS` (lump sum) and package codes such as
227
+ `XPP` are.
228
+
229
+ The business level checks the rule on every document, without a feature flag: the unit codes of
230
+ the invoiced, credited, billed and base quantities (BT-130, BT-150) of UBL, XRechnung, Factur-X and
231
+ ZUGFeRD documents, and the lines of an invoice built in code. A failure is an error with the code
232
+ `BR-CL-23` and the location of the attribute or line. Whitespace around a code in the XML is
233
+ ignored, as in the official rule; the list comparison itself is exact.
234
+
235
+ The list is exported, so an application can check a unit before it builds a document:
236
+
237
+ ```ts
238
+ import { en16931UnitCodeList, isEn16931UnitCode } from '@fin.cx/einvoice';
239
+
240
+ isEn16931UnitCode('HUR'); // true
241
+ isEn16931UnitCode('HOURS'); // false
242
+ isEn16931UnitCode('hur'); // false: codes are compared exactly
243
+ en16931UnitCodeList.codes.length; // 2162, a frozen array in the order of the rule
244
+ en16931UnitCodeList.source; // 'EN 16931 validation artefacts (CEN/TC 434) validation-1.3.16'
245
+ ```
246
+
247
+ The 2162 codes are the BR-CL-23 list of the EN 16931 validation artefacts of CEN/TC 434, release
248
+ validation-1.3.16 (https://github.com/ConnectingEurope/eInvoicing-EN16931), reproduced unaltered.
249
+ The source files are licensed under EUPL-1.2; the codes are those of UN/ECE Recommendations 20 and
250
+ 21.
251
+ `createItem()` gives a line without a unit the code `C62` (one).
252
+
253
+ A line needs a unit (BR-23). A document whose quantity carries no `unitCode` reads with an empty
254
+ `unitType`, never with a unit it did not state, and the business level reports BR-23 with the
255
+ location of the line. An empty `unitType` is written without the attribute, so the unit stays
256
+ absent when the document is exported again.
257
+
258
+ A present but empty attribute, `unitCode=""`, fails BR-CL-23, as in the official rule. It reads
259
+ with an empty `unitType` too, so after a round trip the document fails BR-23 instead. At the
260
+ business level the `EN16931_BUSINESS_RULES` and `CODE_LIST_VALIDATION` flags leave both unit rules
261
+ to the document check, so each line is reported once.
262
+
221
263
  ### Convert between supported XML targets
222
264
 
223
265
  ```ts
@@ -280,6 +322,7 @@ Top-level exports from `@fin.cx/einvoice` include:
280
322
  - `createEInvoice()`
281
323
  - `validateXml(xml, level?)`
282
324
  - `getDocumentTypeCode(accountingDocType)`, `getAccountingDocType(typeCode)`, `isAccountingDocType(value)`
325
+ - `en16931UnitCodeList`, `isEn16931UnitCode(value)` (the BR-CL-23 unit code list)
283
326
  - `FormatDetector`
284
327
  - `PDFExtractor`, `PDFEmbedder`
285
328
  - `DecoderFactory`, `EncoderFactory`, `ValidatorFactory`
@@ -287,7 +330,7 @@ Top-level exports from `@fin.cx/einvoice` include:
287
330
  - `UBLBase*` and `CIIBase*` extension classes
288
331
  - `FacturX*` and `ZUGFeRD*` format-specific classes
289
332
  - `ValidationLevel`, `InvoiceFormat`
290
- - exported types like `TAccountingDoc`, `TAccountingDocType`, `TInvoice`, `TCreditNote`, `TDebitNote`, `TSelfBilledInvoice`, `ValidationResult`, `ValidationError`, `ExportFormat`, `IPdf`, `EInvoiceOptions`
333
+ - exported types like `TAccountingDoc`, `TAccountingDocType`, `TInvoice`, `TCreditNote`, `TDebitNote`, `TSelfBilledInvoice`, `IEn16931UnitCodeList`, `ValidationResult`, `ValidationError`, `ExportFormat`, `IPdf`, `EInvoiceOptions`
291
334
 
292
335
  ## Validation resources and install-time behavior
293
336
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/einvoice',
6
- version: '8.0.0',
6
+ version: '8.1.0',
7
7
  description: 'A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.'
8
8
  }
package/ts/einvoice.ts CHANGED
@@ -32,6 +32,7 @@ import { ValidatorFactory } from './formats/factories/validator.factory.js';
32
32
  // Import format detector
33
33
  import { FormatDetector } from './formats/utils/format.detector.js';
34
34
  import { isAccountingDocType } from './formats/utils/document.typecode.js';
35
+ import { meetsUnitCodeRule } from './formats/utils/unit.codes.js';
35
36
 
36
37
  import {
37
38
  FACTURX_ATTACHMENT_DESCRIPTION,
@@ -517,6 +518,14 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
517
518
  result = this.validateDecodedInvoice(level);
518
519
  }
519
520
 
521
+ // At the business level the document check covers the unit rules (BR-23, BR-CL-23) on every
522
+ // line. The feature flag validators below check the lines again on the decoded model, which
523
+ // cannot tell an absent unit from an empty unitCode="" attribute, so there they would report
524
+ // a second, possibly different rule for the same line. Below the business level the flags
525
+ // are the only unit checks and report them.
526
+ const isDocumentUnitRule = (ruleId: string): boolean =>
527
+ level === ValidationLevel.BUSINESS && (ruleId === 'BR-23' || ruleId === 'BR-CL-23');
528
+
520
529
  // Enhanced validation with feature flags
521
530
  if (options?.featureFlags?.includes('EN16931_BUSINESS_RULES')) {
522
531
  const businessRulesValidator = new EN16931BusinessRulesValidator();
@@ -526,6 +535,7 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
526
535
  result.errors = result.errors.concat(
527
536
  businessResults
528
537
  .filter(r => r.severity === 'error')
538
+ .filter(r => !isDocumentUnitRule(r.ruleId))
529
539
  .map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
530
540
  );
531
541
 
@@ -548,6 +558,7 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
548
558
  result.errors = result.errors.concat(
549
559
  codeListResults
550
560
  .filter(r => r.severity === 'error')
561
+ .filter(r => !isDocumentUnitRule(r.ruleId))
551
562
  .map(r => ({ code: r.ruleId, message: r.message, field: r.field }))
552
563
  );
553
564
  }
@@ -576,6 +587,27 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
576
587
  this.createValidationError(message)
577
588
  );
578
589
 
590
+ if (level === ValidationLevel.BUSINESS) {
591
+ // BR-23 and BR-CL-23: every line has a unit code, and it is a code of UN/ECE Recommendation 20
592
+ // with the Recommendation 21 extension, as the XML validators check it on an exported document
593
+ (invoice.items ?? []).forEach((item, index) => {
594
+ if (!item.unitType) {
595
+ // BR-23: the line has no unit code at all
596
+ errors.push({
597
+ code: 'BR-23',
598
+ message: 'An Invoice line (BG-25) shall have an Invoiced quantity unit of measure code (BT-130)',
599
+ location: `items[${index}].unitType`
600
+ });
601
+ } else if (!meetsUnitCodeRule(item.unitType)) {
602
+ errors.push({
603
+ code: 'BR-CL-23',
604
+ message: `Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension; found "${item.unitType}"`,
605
+ location: `items[${index}].unitType`
606
+ });
607
+ }
608
+ });
609
+ }
610
+
579
611
  return {
580
612
  valid: errors.length === 0,
581
613
  errors,
@@ -793,7 +825,7 @@ export class EInvoice implements TAccountingDocEnvelope<TAccountingDocType, {}>
793
825
  position: data.position || this.items.length + 1,
794
826
  name: data.name || '',
795
827
  articleNumber: data.articleNumber,
796
- unitType: data.unitType || 'unit',
828
+ unitType: data.unitType || 'C62',
797
829
  unitQuantity: data.unitQuantity || 1,
798
830
  unitNetPrice: data.unitNetPrice || 0,
799
831
  vatPercentage: data.vatPercentage || 0
@@ -61,4 +61,32 @@ export abstract class BaseValidator {
61
61
  location
62
62
  });
63
63
  }
64
+
65
+ /**
66
+ * The path of an element in the document, with the position of each step among its siblings
67
+ * of the same name, for example `/Invoice/cac:InvoiceLine[2]/cbc:InvoicedQuantity`.
68
+ */
69
+ protected locationOf(element: Element): string {
70
+ const steps: string[] = [];
71
+ let current: Element | null = element;
72
+ while (current) {
73
+ const name = current.nodeName;
74
+ let position = 1;
75
+ let count = 0;
76
+ const parent: Node | null = current.parentNode;
77
+ if (parent) {
78
+ for (let child = parent.firstChild; child; child = child.nextSibling) {
79
+ if (child.nodeType === 1 && child.nodeName === name) {
80
+ count++;
81
+ if (child === current) {
82
+ position = count;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ steps.unshift(count > 1 ? `${name}[${position}]` : name);
88
+ current = parent && parent.nodeType === 1 ? parent as Element : null;
89
+ }
90
+ return `/${steps.join('/')}`;
91
+ }
64
92
  }
@@ -3,6 +3,7 @@ import { ValidationLevel } from '../../interfaces/common.js';
3
3
  import type { ValidationResult } from '../../interfaces/common.js';
4
4
  import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
5
5
  import { DOMParser, xpath } from '../../plugins.js';
6
+ import { meetsUnitCodeRule } from '../utils/unit.codes.js';
6
7
 
7
8
  const ciiValidatorParser = new DOMParser();
8
9
  const ciiValidatorNamespaces = {
@@ -113,6 +114,52 @@ export abstract class CIIBaseValidator extends BaseValidator {
113
114
  */
114
115
  protected abstract validateStructure(): boolean;
115
116
 
117
+ /**
118
+ * BR-23: every invoice line has a unit code on its billed quantity.
119
+ * @returns True if every line has one
120
+ */
121
+ protected validateLineUnitCodePresence(): boolean {
122
+ // BR-23: every invoice line (BG-25) has an invoiced quantity unit of measure code (BT-130)
123
+ const lines = this.select('//ram:IncludedSupplyChainTradeLineItem', this.doc) as Element[];
124
+ let valid = true;
125
+ for (const line of lines) {
126
+ if (!this.exists('./ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', line)) {
127
+ this.addError(
128
+ 'BR-23',
129
+ 'An Invoice line (BG-25) shall have an Invoiced quantity unit of measure code (BT-130)',
130
+ this.locationOf(line)
131
+ );
132
+ valid = false;
133
+ }
134
+ }
135
+ return valid;
136
+ }
137
+
138
+ /**
139
+ * BR-CL-23: every unit code of a billed or basis quantity (BT-130, BT-150) is a code of
140
+ * UN/ECE Recommendation 20 with the Recommendation 21 extension.
141
+ * @returns True if every unit code passed
142
+ */
143
+ protected validateUnitCodes(): boolean {
144
+ const quantities = this.select(
145
+ '//ram:BilledQuantity[@unitCode] | //ram:BasisQuantity[@unitCode]',
146
+ this.doc
147
+ ) as Element[];
148
+ let valid = true;
149
+ for (const quantity of quantities) {
150
+ const unitCode = quantity.getAttribute('unitCode') ?? '';
151
+ if (!meetsUnitCodeRule(unitCode)) {
152
+ this.addError(
153
+ 'BR-CL-23',
154
+ `Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension; found "${unitCode}"`,
155
+ `${this.locationOf(quantity)}/@unitCode`
156
+ );
157
+ valid = false;
158
+ }
159
+ }
160
+ return valid;
161
+ }
162
+
116
163
  /**
117
164
  * Detects the CII profile from the XML
118
165
  */
@@ -191,7 +191,8 @@ export class FacturXDecoder extends CIIBaseDecoder {
191
191
  const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
192
192
  const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
193
193
  const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
194
- const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
194
+ // an absent unit code (BT-130) stays absent, so that BR-23 reports it
195
+ const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
195
196
  const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
196
197
  const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
197
198
 
@@ -431,7 +431,10 @@ export class FacturXEncoder extends CIIBaseEncoder {
431
431
  const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
432
432
  const quantityElement = doc.createElement('ram:BilledQuantity');
433
433
  quantityElement.textContent = item.unitQuantity.toString();
434
- quantityElement.setAttribute('unitCode', item.unitType);
434
+ // a line without a unit is written without one, so that BR-23 reports it
435
+ if (item.unitType) {
436
+ quantityElement.setAttribute('unitCode', item.unitType);
437
+ }
435
438
  deliveryElement.appendChild(quantityElement);
436
439
  lineItemElement.appendChild(deliveryElement);
437
440
 
@@ -24,14 +24,14 @@ export class FacturXValidator extends CIIBaseValidator {
24
24
  ];
25
25
 
26
26
  for (const section of sections) {
27
- if (!this.exists(section)) {
27
+ if (!this.exists(`/rsm:CrossIndustryInvoice/${section}`)) {
28
28
  this.addError('FX-STRUCT-1', `Required section ${section} is missing`, '/rsm:CrossIndustryInvoice');
29
29
  valid = false;
30
30
  }
31
31
  }
32
32
 
33
33
  // Check for SupplyChainTradeTransaction sections
34
- if (this.exists('rsm:SupplyChainTradeTransaction')) {
34
+ if (this.exists('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction')) {
35
35
  const tradeSubsections = [
36
36
  'ram:ApplicableHeaderTradeAgreement',
37
37
  'ram:ApplicableHeaderTradeDelivery',
@@ -39,7 +39,7 @@ export class FacturXValidator extends CIIBaseValidator {
39
39
  ];
40
40
 
41
41
  for (const subsection of tradeSubsections) {
42
- if (!this.exists(`rsm:SupplyChainTradeTransaction/${subsection}`)) {
42
+ if (!this.exists(`/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/${subsection}`)) {
43
43
  this.addError('FX-STRUCT-2', `Required subsection ${subsection} is missing`,
44
44
  '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction');
45
45
  valid = false;
@@ -70,6 +70,12 @@ export class FacturXValidator extends CIIBaseValidator {
70
70
  // and/or the Seller tax representative VAT identifier (BT-63).
71
71
  valid = this.validateSellerVatIdentifier() && valid;
72
72
 
73
+ // BR-23: every line has a unit code
74
+ valid = this.validateLineUnitCodePresence() && valid;
75
+
76
+ // BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
77
+ valid = this.validateUnitCodes() && valid;
78
+
73
79
  return valid;
74
80
  }
75
81
 
@@ -195,7 +195,8 @@ export class ZUGFeRDDecoder extends CIIBaseDecoder {
195
195
  const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
196
196
  const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
197
197
  const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
198
- const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
198
+ // an absent unit code (BT-130) stays absent, so that BR-23 reports it
199
+ const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
199
200
  const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
200
201
  const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
201
202
 
@@ -599,7 +599,10 @@ export class ZUGFeRDEncoder extends CIIBaseEncoder {
599
599
  const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
600
600
  const quantityElement = doc.createElement('ram:BilledQuantity');
601
601
  quantityElement.textContent = item.unitQuantity.toString();
602
- quantityElement.setAttribute('unitCode', item.unitType);
602
+ // a line without a unit is written without one, so that BR-23 reports it
603
+ if (item.unitType) {
604
+ quantityElement.setAttribute('unitCode', item.unitType);
605
+ }
603
606
  deliveryElement.appendChild(quantityElement);
604
607
  lineItemElement.appendChild(deliveryElement);
605
608
 
@@ -206,7 +206,8 @@ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
206
206
  const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
207
207
  const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
208
208
  const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
209
- const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
209
+ // an absent unit code (BT-130) stays absent, so that BR-23 reports it
210
+ const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode);
210
211
  const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
211
212
  const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
212
213
 
@@ -24,8 +24,9 @@ export class ZUGFeRDValidator extends CIIBaseValidator {
24
24
  * @returns True if business validation passed
25
25
  */
26
26
  protected validateBusinessRules(): boolean {
27
- // Implement ZUGFeRD-specific business rules
28
- // For now, we'll just use the base CII validation
29
- return true;
27
+ // BR-23: every line has a unit code
28
+ const linesHaveUnits = this.validateLineUnitCodePresence();
29
+ // BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
30
+ return this.validateUnitCodes() && linesHaveUnits;
30
31
  }
31
32
  }
@@ -100,7 +100,8 @@ export class XMLToEInvoiceConverter {
100
100
  position: i,
101
101
  name: this.getElementTextFromNode(line, 'cbc:Name') || `Item ${i + 1}`,
102
102
  unitQuantity: parseFloat(this.getElementTextFromNode(line, 'cbc:InvoicedQuantity') || '1'),
103
- unitType: 'C62',
103
+ // the unit code (BT-130) the document states, or none, which BR-23 reports
104
+ unitType: this.getAttributeFromNode(line, 'cbc:InvoicedQuantity', 'unitCode'),
104
105
  unitNetPrice: parseFloat(this.getElementTextFromNode(line, 'cbc:PriceAmount') || '100'),
105
106
  vatPercentage: parseFloat(this.getElementTextFromNode(line, 'cbc:Percent') || '19')
106
107
  };
@@ -118,6 +119,15 @@ export class XMLToEInvoiceConverter {
118
119
  return mockInvoice as unknown as EInvoice;
119
120
  }
120
121
 
122
+ /**
123
+ * Helper to get an attribute of the first matching element below a node, or '' when the element
124
+ * or the attribute is absent
125
+ */
126
+ private getAttributeFromNode(node: Element, tagName: string, attributeName: string): string {
127
+ const element = node.getElementsByTagName(tagName)[0];
128
+ return element?.getAttribute(attributeName) ?? '';
129
+ }
130
+
121
131
  /**
122
132
  * Helper to get element text from a node
123
133
  */
@@ -442,7 +442,7 @@ export class SemanticModelAdapter {
442
442
  identifier: (index + 1).toString(),
443
443
  note: (item as any).description || (item as any).text || '',
444
444
  invoicedQuantity: item.unitQuantity,
445
- invoicedQuantityUnitOfMeasureCode: item.unitType || 'C62',
445
+ invoicedQuantityUnitOfMeasureCode: item.unitType,
446
446
  lineExtensionAmount: item.unitNetPrice * item.unitQuantity,
447
447
  purchaseOrderLineReference: (item as any).purchaseOrderLineRef,
448
448
  buyerAccountingReference: (item as any).buyerAccountingRef,
@@ -148,6 +148,12 @@ export class EN16931UBLValidator extends UBLBaseValidator {
148
148
  valid = false;
149
149
  }
150
150
 
151
+ // BR-23: every line has a unit code
152
+ valid = this.validateLineUnitCodePresence() && valid;
153
+
154
+ // BR-CL-23: unit codes of UN/ECE Recommendation 20 with the Recommendation 21 extension
155
+ valid = this.validateUnitCodes() && valid;
156
+
151
157
  // Validate calculation rules if we have the necessary data
152
158
  if (this.exists('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount')) {
153
159
  valid = this.validateCalculationRules() && valid;
@@ -459,7 +459,10 @@ export class UBLEncoder extends UBLBaseEncoder {
459
459
 
460
460
  // Invoiced quantity
461
461
  const quantityElement = doc.createElement(creditNote ? 'cbc:CreditedQuantity' : 'cbc:InvoicedQuantity');
462
- quantityElement.setAttribute('unitCode', item.unitType);
462
+ // a line without a unit is written without one, so that BR-23 reports it
463
+ if (item.unitType) {
464
+ quantityElement.setAttribute('unitCode', item.unitType);
465
+ }
463
466
  quantityElement.textContent = item.unitQuantity.toString();
464
467
  invoiceLineNode.appendChild(quantityElement);
465
468