@aidc-toolkit/gs1 1.0.28-beta → 1.0.32-beta

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 (102) hide show
  1. package/dist/gcp-length-cache.d.ts +42 -0
  2. package/dist/gcp-length-cache.d.ts.map +1 -0
  3. package/dist/gcp-length-cache.js +96 -0
  4. package/dist/gcp-length-cache.js.map +1 -0
  5. package/dist/gcp-length-data.d.ts +54 -0
  6. package/dist/gcp-length-data.d.ts.map +1 -0
  7. package/dist/gcp-length-data.js +29 -0
  8. package/dist/gcp-length-data.js.map +1 -0
  9. package/dist/gcp-length.d.ts +61 -0
  10. package/dist/gcp-length.d.ts.map +1 -0
  11. package/dist/gcp-length.js +301 -0
  12. package/dist/gcp-length.js.map +1 -0
  13. package/dist/gtin-creator.d.ts +0 -17
  14. package/dist/gtin-creator.d.ts.map +1 -1
  15. package/dist/gtin-creator.js +1 -93
  16. package/dist/gtin-creator.js.map +1 -1
  17. package/dist/gtin-validator.d.ts +1 -46
  18. package/dist/gtin-validator.d.ts.map +1 -1
  19. package/dist/gtin-validator.js +31 -125
  20. package/dist/gtin-validator.js.map +1 -1
  21. package/dist/identifier-validator.d.ts +1 -4
  22. package/dist/identifier-validator.d.ts.map +1 -1
  23. package/dist/identifier-validator.js +2 -5
  24. package/dist/identifier-validator.js.map +1 -1
  25. package/dist/index.d.ts +5 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/locale/en/locale-resources.d.ts +2 -1
  30. package/dist/locale/en/locale-resources.d.ts.map +1 -1
  31. package/dist/locale/en/locale-resources.js +3 -2
  32. package/dist/locale/en/locale-resources.js.map +1 -1
  33. package/dist/locale/fr/locale-resources.d.ts +2 -1
  34. package/dist/locale/fr/locale-resources.d.ts.map +1 -1
  35. package/dist/locale/fr/locale-resources.js +3 -2
  36. package/dist/locale/fr/locale-resources.js.map +1 -1
  37. package/dist/locale/i18n.d.ts +0 -3
  38. package/dist/locale/i18n.d.ts.map +1 -1
  39. package/dist/locale/i18n.js +2 -5
  40. package/dist/locale/i18n.js.map +1 -1
  41. package/dist/non-numeric-identifier-validator.js +1 -1
  42. package/dist/non-numeric-identifier-validator.js.map +1 -1
  43. package/dist/numeric-identifier-validator.js +2 -2
  44. package/dist/numeric-identifier-validator.js.map +1 -1
  45. package/dist/prefix-manager.d.ts +35 -0
  46. package/dist/prefix-manager.d.ts.map +1 -1
  47. package/dist/prefix-manager.js +56 -0
  48. package/dist/prefix-manager.js.map +1 -1
  49. package/dist/prefix-validator.d.ts +5 -4
  50. package/dist/prefix-validator.d.ts.map +1 -1
  51. package/dist/prefix-validator.js +18 -22
  52. package/dist/prefix-validator.js.map +1 -1
  53. package/dist/serializable-numeric-identifier-validator.d.ts +26 -0
  54. package/dist/serializable-numeric-identifier-validator.d.ts.map +1 -1
  55. package/dist/serializable-numeric-identifier-validator.js +19 -0
  56. package/dist/serializable-numeric-identifier-validator.js.map +1 -1
  57. package/dist/variable-measure.d.ts +68 -0
  58. package/dist/variable-measure.d.ts.map +1 -0
  59. package/dist/variable-measure.js +210 -0
  60. package/dist/variable-measure.js.map +1 -0
  61. package/dist/verified-by-gs1.d.ts +22 -0
  62. package/dist/verified-by-gs1.d.ts.map +1 -0
  63. package/dist/verified-by-gs1.js +46 -0
  64. package/dist/verified-by-gs1.js.map +1 -0
  65. package/package.json +8 -8
  66. package/src/gcp-length-cache.ts +117 -0
  67. package/src/gcp-length-data.ts +68 -0
  68. package/src/gcp-length.ts +418 -0
  69. package/src/gtin-creator.ts +1 -117
  70. package/src/gtin-validator.ts +42 -173
  71. package/src/identifier-validator.ts +2 -5
  72. package/src/index.ts +7 -1
  73. package/src/locale/en/locale-resources.ts +3 -2
  74. package/src/locale/fr/locale-resources.ts +3 -2
  75. package/src/locale/i18n.ts +2 -5
  76. package/src/non-numeric-identifier-validator.ts +1 -1
  77. package/src/numeric-identifier-validator.ts +2 -2
  78. package/src/prefix-manager.ts +65 -0
  79. package/src/prefix-validator.ts +19 -23
  80. package/src/serializable-numeric-identifier-validator.ts +36 -0
  81. package/src/variable-measure.ts +268 -0
  82. package/src/verified-by-gs1.ts +54 -0
  83. package/test/creator.test.ts +5 -5
  84. package/test/data/gcpprefixformatlist-1.json +662625 -0
  85. package/test/data/gcpprefixformatlist-2.json +735431 -0
  86. package/test/gcp-length.test.ts +405 -0
  87. package/test/gtin-creator.ts +4 -4
  88. package/test/gtin-validator.test.ts +205 -113
  89. package/test/gtin-validator.ts +30 -0
  90. package/test/identifier-creator.ts +6 -6
  91. package/test/non-numeric-identifier-creator.ts +0 -8
  92. package/test/non-serializable-numeric-identifier-creator.ts +4 -54
  93. package/test/numeric-identifier-creator.ts +4 -4
  94. package/test/prefix-manager.test.ts +5 -5
  95. package/test/serializable-numeric-identifier-creator.ts +32 -19
  96. package/test/validator.test.ts +6 -6
  97. package/test/variable-measure-rcn.test.ts +63 -68
  98. package/test/verified-by-gs1.test.ts +55 -0
  99. package/tsconfig-src.json +7 -1
  100. package/tsconfig-src.tsbuildinfo +1 -0
  101. package/tsconfig-tsup.json +7 -0
  102. package/tsconfig.json +1 -0
@@ -1,5 +1,6 @@
1
1
  import { type CharacterSetValidation, NUMERIC_CREATOR } from "@aidc-toolkit/utility";
2
- import { checkDigit, hasValidCheckDigit, isValidPriceOrWeightCheckDigit } from "./check.js";
2
+ import type { ParseKeys } from "i18next";
3
+ import { checkDigit, hasValidCheckDigit } from "./check.js";
3
4
  import type { GTINDescriptor } from "./gtin-descriptor.js";
4
5
  import { type GTINBaseLength, GTINLengths } from "./gtin-length.js";
5
6
  import type { GTINType } from "./gtin-type.js";
@@ -39,21 +40,6 @@ export type GTINLevelKey = keyof typeof GTINLevels;
39
40
  */
40
41
  export type GTINLevel = typeof GTINLevels[GTINLevelKey];
41
42
 
42
- /**
43
- * Restricted Circulation Number reference.
44
- */
45
- export interface RCNReference {
46
- /**
47
- * Item reference.
48
- */
49
- itemReference: number;
50
-
51
- /**
52
- * Price or weight (whole number only).
53
- */
54
- priceOrWeight: number;
55
- }
56
-
57
43
  /**
58
44
  * GTIN validator.
59
45
  */
@@ -75,6 +61,12 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
75
61
  maximumLength: 8
76
62
  };
77
63
 
64
+ static readonly #ERROR_MESSAGE_PARSE_KEYS: Record<GTINLevel, ParseKeys | undefined> = {
65
+ [GTINLevels.Any]: undefined,
66
+ [GTINLevels.RetailConsumer]: "Identifier.invalidGTINAtRetail",
67
+ [GTINLevels.OtherThanRetailConsumer]: "Identifier.invalidGTINAtOtherThanRetail"
68
+ };
69
+
78
70
  /**
79
71
  * Prefix type.
80
72
  */
@@ -104,9 +96,9 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
104
96
  /**
105
97
  * @inheritDoc
106
98
  */
107
- protected override validatePrefix(partialIdentifier: string, positionOffset?: number): void {
99
+ protected override validatePrefix(partialIdentifier: string): void {
108
100
  // Delegate to prefix validator requiring exact match for prefix type.
109
- PrefixValidator.validate(this.prefixType, false, false, partialIdentifier, true, true, positionOffset);
101
+ PrefixValidator.validate(this.prefixType, false, false, partialIdentifier, true, true);
110
102
  }
111
103
 
112
104
  /**
@@ -199,18 +191,16 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
199
191
  * GTIN-14.
200
192
  */
201
193
  static convertToGTIN14(indicatorDigit: string, gtin: string): string {
202
- GTINValidator.validateAny(gtin);
203
-
204
194
  NUMERIC_CREATOR.validate(indicatorDigit, GTINValidator.#OPTIONAL_INDICATOR_DIGIT_VALIDATION);
205
195
 
206
196
  // Check digit doesn't change by prepending zeros.
207
- let gtin14 = gtin.padStart(GTINLengths.GTIN14, "0");
197
+ let gtin14 = GTINValidator.normalize(gtin).padStart(GTINLengths.GTIN14, "0");
208
198
 
209
199
  // If indicator digit provided and is different, recalculate the check digit.
210
- if (indicatorDigit.length !== 0 && indicatorDigit !== gtin14.charAt(0)) {
211
- const partialGTIN14 = indicatorDigit + gtin14.substring(1, GTINLengths.GTIN14 - 1);
200
+ if (indicatorDigit.length !== 0 && !gtin14.startsWith(indicatorDigit)) {
201
+ const partialGTIN14 = `${indicatorDigit}${gtin14.substring(1, GTINLengths.GTIN14 - 1)}`;
212
202
 
213
- gtin14 = partialGTIN14 + checkDigit(partialGTIN14);
203
+ gtin14 = `${partialGTIN14}${checkDigit(partialGTIN14)}`;
214
204
  }
215
205
 
216
206
  return gtin14;
@@ -235,7 +225,7 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
235
225
  let normalizedGTIN: string;
236
226
 
237
227
  switch (gtinLength) {
238
- case GTINLengths.GTIN13 as number:
228
+ case GTINLengths.GTIN13:
239
229
  if (!gtin.startsWith("0")) {
240
230
  // GTIN is GTIN-13.
241
231
  normalizedGTIN = gtin;
@@ -250,12 +240,12 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
250
240
  }
251
241
  break;
252
242
 
253
- case GTINLengths.GTIN12 as number:
243
+ case GTINLengths.GTIN12:
254
244
  // GTIN is GTIN-12.
255
245
  normalizedGTIN = gtin;
256
246
  break;
257
247
 
258
- case GTINLengths.GTIN8 as number:
248
+ case GTINLengths.GTIN8:
259
249
  if (!gtin.startsWith("0")) {
260
250
  // GTIN is GTIN-8.
261
251
  normalizedGTIN = gtin;
@@ -265,7 +255,7 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
265
255
  }
266
256
  break;
267
257
 
268
- case GTINLengths.GTIN14 as number:
258
+ case GTINLengths.GTIN14:
269
259
  if (!gtin.startsWith("0")) {
270
260
  // GTIN is GTIN-14.
271
261
  normalizedGTIN = gtin;
@@ -306,43 +296,46 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
306
296
  // Assume length-validated GTIN is the GTIN (true for all except zero-suppressed GTIN-12).
307
297
  let lengthValidatedGTIN = gtin;
308
298
 
299
+ let prefixType: PrefixType | undefined;
300
+ let allowUPCCompanyPrefix = false;
301
+ let allowGS18Prefix = false;
302
+ let validatePrefix = gtin;
309
303
  let gtinLevelRestriction: GTINLevel;
310
304
 
311
305
  switch (gtin.length) {
312
- case GTINLengths.GTIN13 as number:
313
- if (gtin.startsWith("0")) {
314
- throw new RangeError(i18nextGS1.t("Identifier.invalidGTIN13AtRetail"));
315
- }
316
-
317
- // Validate prefix requiring exact match for prefix type.
318
- PrefixValidator.validate(PrefixTypes.GS1CompanyPrefix, false, false, gtin, true, true);
319
-
306
+ case GTINLengths.GTIN13:
307
+ // Validate prefix with restrictions on prefix type depending on GTIN level.
308
+ prefixType = PrefixTypes.GS1CompanyPrefix;
309
+ allowUPCCompanyPrefix = gtinLevel !== GTINLevels.RetailConsumer;
310
+ allowGS18Prefix = gtinLevel === GTINLevels.Any;
320
311
  gtinLevelRestriction = GTINLevels.Any;
321
312
  break;
322
313
 
323
- case GTINLengths.GTIN12 as number:
314
+ case GTINLengths.GTIN12:
324
315
  // Validate prefix requiring exact match for prefix type.
325
- PrefixValidator.validate(PrefixTypes.UPCCompanyPrefix, false, false, gtin, true, true);
326
-
316
+ prefixType = PrefixTypes.UPCCompanyPrefix;
327
317
  gtinLevelRestriction = GTINLevels.Any;
328
318
  break;
329
319
 
330
- case GTINLengths.GTIN8 as number:
320
+ case GTINLengths.GTIN8:
331
321
  // Zero-suppressed GTIN-12 always starts with 0.
332
322
  if (!gtin.startsWith("0")) {
333
323
  // Validate prefix requiring exact match for prefix type.
334
- PrefixValidator.validate(PrefixTypes.GS18Prefix, false, false, gtin, true, true);
324
+ prefixType = PrefixTypes.GS18Prefix;
335
325
  } else {
336
326
  lengthValidatedGTIN = GTINValidator.zeroExpand(gtin);
327
+ prefixType = undefined;
337
328
  }
338
329
 
339
330
  gtinLevelRestriction = GTINLevels.RetailConsumer;
340
331
  break;
341
332
 
342
- case GTINLengths.GTIN14 as number:
343
- // Validate prefix supporting any prefix type.
344
- PrefixValidator.validate(PrefixTypes.GS1CompanyPrefix, true, true, gtin.substring(1), true, true);
345
-
333
+ case GTINLengths.GTIN14:
334
+ // Validate prefix with restrictions on prefix type depending on GTIN level.
335
+ prefixType = PrefixTypes.GS1CompanyPrefix;
336
+ allowUPCCompanyPrefix = true;
337
+ allowGS18Prefix = gtinLevel !== GTINLevels.OtherThanRetailConsumer || !gtin.startsWith("0");
338
+ validatePrefix = gtin.substring(1);
346
339
  gtinLevelRestriction = GTINLevels.OtherThanRetailConsumer;
347
340
  break;
348
341
 
@@ -350,6 +343,10 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
350
343
  throw new RangeError(i18nextGS1.t("Identifier.invalidGTINLength"));
351
344
  }
352
345
 
346
+ if (prefixType !== undefined) {
347
+ PrefixValidator.validate(prefixType, allowUPCCompanyPrefix, allowGS18Prefix, validatePrefix, true, true, GTINValidator.#ERROR_MESSAGE_PARSE_KEYS[gtinLevel]);
348
+ }
349
+
353
350
  // Validating the check digit will also validate the characters.
354
351
  if (!hasValidCheckDigit(lengthValidatedGTIN)) {
355
352
  throw new RangeError(i18nextGS1.t("Identifier.invalidCheckDigit"));
@@ -374,134 +371,6 @@ export class GTINValidator extends NumericIdentifierValidator<GTINType> implemen
374
371
 
375
372
  GTINValidator.validateAny(gtin14);
376
373
  }
377
-
378
- /**
379
- * Parse a Restricted Circulation Number (RCN) using a variable measure trade item format. The format is a 12- or
380
- * 13-character string (for RCN-12 or RCN-13 respectively), containing the following:
381
- *
382
- * - '2' - The first character of the RCN.
383
- * - '0'-'9' - The second character of the RCN (RCN-13 only).
384
- * - 'I' - One or more, in sequence, for the item reference.
385
- * - 'P' - One or more, in sequence, for the price or weight.
386
- * - 'V' - Zero or one, for the price or weight check digit.
387
- * - 'C' - The check digit of the entire RCN.
388
- *
389
- * The 'I', 'P', and 'V' formats may be in any order.
390
- *
391
- * Some examples:
392
- *
393
- * - 2IIIIIVPPPPC - RCN-12 with a five-digit item reference, a price or weight check digit, and a four-digit price
394
- * or weight.
395
- * - 23IIIIVPPPPPC - RCN-13 with a four-digit item reference, a price or weight check digit, and a five-digit price
396
- * or weight.
397
- * - 2IIIIIIPPPPC - RCN-12 with a six-digit item reference and a four-digit price or eight.
398
- * - 29IIIIIPPPPPC - RCN-13 with a five-digit item reference and a five-digit price or weight.
399
- *
400
- * @param format
401
- * Format.
402
- *
403
- * @param rcn
404
- * RCN.
405
- *
406
- * @returns
407
- * RCN reference.
408
- */
409
- static parseVariableMeasureRCN(format: string, rcn: string): RCNReference {
410
- const formatLength = format.length;
411
-
412
- if (rcn.length !== formatLength) {
413
- throw new RangeError(i18nextGS1.t("Identifier.invalidRCNLength"));
414
- }
415
-
416
- let validFormat = formatLength === 12 || formatLength === 13;
417
- let validRCNPrefix = true;
418
-
419
- let buildingItemReference = false;
420
- let itemReference = "";
421
-
422
- let buildingPriceOrWeight = false;
423
- let priceOrWeight = "";
424
-
425
- let priceOrWeightCheckDigit = "";
426
-
427
- for (let index = 0; validFormat && index < formatLength; index++) {
428
- const formatChar = format.charAt(index);
429
- const rcnChar = rcn.charAt(index);
430
-
431
- if (index === 0) {
432
- validFormat = formatChar === "2";
433
- validRCNPrefix = rcnChar === "2";
434
- } else if (formatLength === 13 && index === 1) {
435
- validFormat = NUMERIC_CREATOR.characterIndex(formatChar) !== undefined;
436
- validRCNPrefix = rcnChar === formatChar;
437
- } else if (index === formatLength - 1) {
438
- validFormat = formatChar === "C";
439
- } else {
440
- switch (formatChar) {
441
- case "I":
442
- if (!buildingItemReference) {
443
- // Item reference can't appear more than once.
444
- validFormat = itemReference === "";
445
-
446
- buildingItemReference = true;
447
- buildingPriceOrWeight = false;
448
- }
449
-
450
- itemReference += rcnChar;
451
- break;
452
-
453
- case "P":
454
- if (!buildingPriceOrWeight) {
455
- // Price or weight can't appear more than once.
456
- validFormat = priceOrWeight === "";
457
-
458
- buildingPriceOrWeight = true;
459
- buildingItemReference = false;
460
- }
461
-
462
- priceOrWeight += rcnChar;
463
- break;
464
-
465
- case "V":
466
- // Price or weight check digit can't appear more than once.
467
- validFormat = priceOrWeightCheckDigit === "";
468
-
469
- buildingItemReference = false;
470
- buildingPriceOrWeight = false;
471
-
472
- priceOrWeightCheckDigit = rcnChar;
473
- break;
474
-
475
- default:
476
- validFormat = false;
477
- break;
478
- }
479
- }
480
- }
481
-
482
- validFormat &&= itemReference !== "" && priceOrWeight !== "";
483
-
484
- if (!validFormat) {
485
- throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasureRCNFormat"));
486
- }
487
-
488
- if (!validRCNPrefix) {
489
- throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasureRCNPrefix"));
490
- }
491
-
492
- if (priceOrWeightCheckDigit !== "" && !isValidPriceOrWeightCheckDigit(priceOrWeight, priceOrWeightCheckDigit)) {
493
- throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasurePriceOrWeight"));
494
- }
495
-
496
- if (!hasValidCheckDigit(rcn)) {
497
- throw new RangeError(i18nextGS1.t("Identifier.invalidCheckDigit"));
498
- }
499
-
500
- return {
501
- itemReference: Number(itemReference),
502
- priceOrWeight: Number(priceOrWeight)
503
- };
504
- }
505
374
  }
506
375
 
507
376
  /**
@@ -120,13 +120,10 @@ export abstract class IdentifierValidator<TIdentifierType extends IdentifierType
120
120
  *
121
121
  * @param partialIdentifier
122
122
  * Partial identifier.
123
- *
124
- * @param positionOffset
125
- * Position offset within a larger string.
126
123
  */
127
- protected validatePrefix(partialIdentifier: string, positionOffset?: number): void {
124
+ protected validatePrefix(partialIdentifier: string): void {
128
125
  // Delegate to prefix validator with support for U.P.C. Company Prefix but not GS1-8 Prefix.
129
- PrefixValidator.validate(this.prefixType, true, false, partialIdentifier, true, this.referenceCharacterSet === ContentCharacterSets.Numeric, positionOffset);
126
+ PrefixValidator.validate(this.prefixType, true, false, partialIdentifier, true, this.referenceCharacterSet === ContentCharacterSets.Numeric);
130
127
  }
131
128
 
132
129
  /**
package/src/index.ts CHANGED
@@ -47,13 +47,15 @@ export * from "./identifier-descriptors.js";
47
47
 
48
48
  export * from "./identifier-validator.js";
49
49
  export * from "./numeric-identifier-validator.js";
50
- export { GTINLevels, type GTINLevelKey, type GTINLevel, type RCNReference, GTINValidator } from "./gtin-validator.js";
50
+ export { GTINLevels, type GTINLevelKey, type GTINLevel, GTINValidator } from "./gtin-validator.js";
51
51
  export * from "./non-gtin-numeric-identifier-validator.js";
52
52
  export * from "./non-serializable-numeric-identifier-validator.js";
53
53
  export * from "./serializable-numeric-identifier-validator.js";
54
54
  export * from "./non-numeric-identifier-validator.js";
55
55
  export * from "./identifier-validators.js";
56
56
 
57
+ export * from "./verified-by-gs1.js";
58
+
57
59
  export type * from "./identifier-creator.js";
58
60
  export type * from "./numeric-identifier-creator.js";
59
61
  export * from "./gtin-creator.js";
@@ -63,4 +65,8 @@ export * from "./serializable-numeric-identifier-creator.js";
63
65
  export * from "./non-numeric-identifier-creator.js";
64
66
  export * from "./identifier-creators.js";
65
67
 
68
+ export * from "./variable-measure.js";
69
+
70
+ export * from "./gcp-length-data.js";
71
+ export * from "./gcp-length-cache.js";
66
72
  export * from "./prefix-manager.js";
@@ -13,7 +13,6 @@ export default {
13
13
  invalidZeroSuppressibleGTIN12: "GTIN-12 not zero-suppressible",
14
14
  invalidZeroSuppressedGTIN12AsGTIN13: "Invalid zero-suppressed GTIN-12 as GTIN-13",
15
15
  invalidZeroSuppressedGTIN12AsGTIN14: "Invalid zero-suppressed GTIN-12 as GTIN-14",
16
- invalidGTIN13AtRetail: "GTIN-13 at retail consumer trade item level can't start with zero",
17
16
  invalidGTINAtRetail: "GTIN not supported at retail consumer trade item level",
18
17
  invalidGTINAtOtherThanRetail: "GTIN not supported at other than retail consumer trade item level",
19
18
  invalidRCNLength: "RCN length must match format length",
@@ -35,6 +34,8 @@ export default {
35
34
  gs1CompanyPrefixCantStartWith000000: "GS1 Company Prefix can't start with \"000000\"",
36
35
  upcCompanyPrefixCantStartWith0000: "U.P.C. Company Prefix can't start with \"0000\"",
37
36
  gs18PrefixCantStartWith0: "GS1-8 Prefix can't start with \"0\"",
38
- identifierTypeNotSupportedByGS18Prefix: "{{identifierType}} not supported by GS1-8 Prefix"
37
+ identifierTypeNotSupportedByGS18Prefix: "{{identifierType}} not supported by GS1-8 Prefix",
38
+ gs1CompanyPrefixLengthDataHTTPError: "HTTP error {{status, number}} loading GS1 Company Prefix length data",
39
+ gs1CompanyPrefixLengthDataNotLoaded: "GS1 Company Prefix length data not loaded"
39
40
  }
40
41
  } as const;
@@ -13,7 +13,6 @@ export default {
13
13
  invalidZeroSuppressibleGTIN12: "Le GTIN-12 ne peut pas être supprimé par zéro",
14
14
  invalidZeroSuppressedGTIN12AsGTIN13: "GTIN-12 non valide avec zéro supprimé en tant que GTIN-13",
15
15
  invalidZeroSuppressedGTIN12AsGTIN14: "GTIN-12 non valide avec zéro supprimé en tant que GTIN-14",
16
- invalidGTIN13AtRetail: "Le GTIN-13 au niveau des articles de consommation au détail ne peut pas commencer par zéro",
17
16
  invalidGTINAtRetail: "Le GTIN n'est pas pris en charge au niveau des articles de consommation au détail",
18
17
  invalidGTINAtOtherThanRetail: "Le GTIN n'est pas pris en charge à d'autres niveaux que ceux des articles de consommation au détail",
19
18
  invalidRCNLength: "La longueur du RCN doit correspondre à la longueur du format",
@@ -35,6 +34,8 @@ export default {
35
34
  gs1CompanyPrefixCantStartWith000000: "Le préfixe de l'entreprise GS1 ne peut pas commencer par \"000000\"",
36
35
  upcCompanyPrefixCantStartWith0000: "Le préfixe de l'entreprise U.P.C. ne peut pas commencer par \"0000\"",
37
36
  gs18PrefixCantStartWith0: "Le préfixe GS1-8 ne peut pas commencer par \"0\"",
38
- identifierTypeNotSupportedByGS18Prefix: "{{identifierType}} non pris en charge par le préfixe GS1-8"
37
+ identifierTypeNotSupportedByGS18Prefix: "{{identifierType}} non pris en charge par le préfixe GS1-8",
38
+ gs1CompanyPrefixLengthDataHTTPError: "Erreur HTTP {{status, number}} lors du chargement des données de longueur du préfixe d'entreprise GS1",
39
+ gs1CompanyPrefixLengthDataNotLoaded: "Les données relatives à la longueur du préfixe d'entreprise GS1 n'ont pas été chargées"
39
40
  }
40
41
  } as const;
@@ -1,4 +1,4 @@
1
- import { i18nCoreInit, type I18nEnvironment } from "@aidc-toolkit/core";
1
+ import { type I18nEnvironment, i18nFinalizeInit } from "@aidc-toolkit/core";
2
2
  import { i18nUtilityInit, utilityResources } from "@aidc-toolkit/utility";
3
3
  import i18next, { type i18n, type Resource } from "i18next";
4
4
  import enLocaleResources from "./en/locale-resources.js";
@@ -34,11 +34,8 @@ export const i18nextGS1: i18n = i18next.createInstance();
34
34
  *
35
35
  * @param debug
36
36
  * Debug setting.
37
- *
38
- * @returns
39
- * Void promise.
40
37
  */
41
38
  export async function i18nGS1Init(environment: I18nEnvironment, debug = false): Promise<void> {
42
39
  await i18nUtilityInit(environment, debug);
43
- await i18nCoreInit(i18nextGS1, environment, debug, gs1NS, utilityResources, gs1Resources);
40
+ await i18nFinalizeInit(i18nextGS1, environment, debug, gs1NS, utilityResources, gs1Resources);
44
41
  }
@@ -30,7 +30,7 @@ export class NonNumericIdentifierValidator extends IdentifierValidator<NonNumeri
30
30
  protected override createErrorMessage(_s: string): string {
31
31
  return i18nextGS1.t("Identifier.referenceCantBeAllNumeric");
32
32
  }
33
- }(/\D/);
33
+ }(/\D/u);
34
34
 
35
35
  /**
36
36
  * True if the identifier requires a check character pair.
@@ -59,9 +59,9 @@ export abstract class NumericIdentifierValidator<TNumericIdentifierType extends
59
59
  validate(identifier: string, validation?: NumericIdentifierValidation): void {
60
60
  // Validate the prefix, with care taken for its position within the identifier.
61
61
  if (this.#prefixPosition === 0) {
62
- super.validatePrefix(identifier, validation?.positionOffset);
62
+ super.validatePrefix(identifier);
63
63
  } else {
64
- super.validatePrefix(identifier.substring(this.#prefixPosition), validation?.positionOffset === undefined ? this.#prefixPosition : validation.positionOffset + this.#prefixPosition);
64
+ super.validatePrefix(identifier.substring(this.#prefixPosition));
65
65
  }
66
66
 
67
67
  // Validate the length.
@@ -1,3 +1,4 @@
1
+ import * as GCPLength from "./gcp-length.js";
1
2
  import type { GTINCreator } from "./gtin-creator.js";
2
3
  import { GTIN_BASE_TYPES } from "./gtin-length.js";
3
4
  import type { GTINType } from "./gtin-type.js";
@@ -17,6 +18,7 @@ import type { PrefixProvider } from "./prefix-provider.js";
17
18
  import { type PrefixType, PrefixTypes } from "./prefix-type.js";
18
19
  import { PrefixValidator } from "./prefix-validator.js";
19
20
  import type { SerializableNumericIdentifierCreator } from "./serializable-numeric-identifier-creator.js";
21
+ import type { GCPLengthCache } from "./gcp-length-cache.js";
20
22
 
21
23
  /**
22
24
  * Prefix manager. This is the core class for identifier creation.
@@ -99,6 +101,11 @@ export class PrefixManager implements PrefixProvider {
99
101
  */
100
102
  readonly #identifierCreators: Partial<IdentifierCreatorsRecord> = {};
101
103
 
104
+ /**
105
+ * GS1 Company Prefix length root.
106
+ */
107
+ static #gcpLengthRoot: GCPLength.Root | undefined = undefined;
108
+
102
109
  /**
103
110
  * Constructor.
104
111
  *
@@ -350,4 +357,62 @@ export class PrefixManager implements PrefixProvider {
350
357
  get gmnCreator(): NonNumericIdentifierCreator {
351
358
  return this.getIdentifierCreator(IdentifierTypes.GMN);
352
359
  }
360
+
361
+ /**
362
+ * Load GS1 Company Prefix length data.
363
+ *
364
+ * @param gcpLengthCache
365
+ * GS1 Company Prefix length cache.
366
+ */
367
+ static async loadGCPLengthData(gcpLengthCache: GCPLengthCache): Promise<void> {
368
+ PrefixManager.#gcpLengthRoot = await GCPLength.loadData(gcpLengthCache);
369
+ }
370
+
371
+ /**
372
+ * Get the length of a GS1 Company Prefix for an identifier.
373
+ *
374
+ * @param identifierType
375
+ * Identifier type.
376
+ *
377
+ * @param identifier
378
+ * Identifier.
379
+ *
380
+ * @returns
381
+ * Length of GS1 Company Prefix, 0 if not a GS1 Company Prefix, or -1 if not found.
382
+ */
383
+ static gcpLength(identifierType: IdentifierType, identifier: string): number {
384
+ if (PrefixManager.#gcpLengthRoot === undefined) {
385
+ throw new RangeError(i18nextGS1.t("Prefix.gs1CompanyPrefixLengthDataNotLoaded"));
386
+ }
387
+
388
+ return GCPLength.getFor(PrefixManager.#gcpLengthRoot, identifierType, identifier);
389
+ }
390
+
391
+ /**
392
+ * Get the date/time the GS1 Company Prefix length data was last updated.
393
+ *
394
+ * @returns
395
+ * Date/time the GS1 Company Prefix length data was last updated.
396
+ */
397
+ static gcpLengthDateTime(): Date {
398
+ if (PrefixManager.#gcpLengthRoot === undefined) {
399
+ throw new RangeError(i18nextGS1.t("Prefix.gs1CompanyPrefixLengthDataNotLoaded"));
400
+ }
401
+
402
+ return PrefixManager.#gcpLengthRoot.dateTime;
403
+ }
404
+
405
+ /**
406
+ * Get the disclaimer for the GS1 Company Prefix length data.
407
+ *
408
+ * @returns
409
+ * Disclaimer for the GS1 Company Prefix length data.
410
+ */
411
+ static gcpLengthDisclaimer(): string {
412
+ if (PrefixManager.#gcpLengthRoot === undefined) {
413
+ throw new RangeError(i18nextGS1.t("Prefix.gs1CompanyPrefixLengthDataNotLoaded"));
414
+ }
415
+
416
+ return PrefixManager.#gcpLengthRoot.disclaimer;
417
+ }
353
418
  }
@@ -2,6 +2,7 @@ import { type CharacterSetValidation, NUMERIC_CREATOR } from "@aidc-toolkit/util
2
2
  import { i18nextGS1 } from "./locale/i18n.js";
3
3
  import type { PrefixProvider } from "./prefix-provider.js";
4
4
  import { type PrefixType, PrefixTypes } from "./prefix-type.js";
5
+ import type { ParseKeys } from "i18next";
5
6
 
6
7
  /**
7
8
  * Prefix validation parameters.
@@ -36,7 +37,7 @@ const GS1_COMPANY_PREFIX_MAXIMUM_LENGTH = 12;
36
37
  /**
37
38
  * U.P.C. Company Prefix minimum length.
38
39
  */
39
- const UPC_COMPANY_PREFIX_MINIMUM_LENGTH = 6;
40
+ const UPC_COMPANY_PREFIX_MINIMUM_LENGTH = 5;
40
41
 
41
42
  /**
42
43
  * U.P.C. Company Prefix maximum length.
@@ -130,31 +131,31 @@ export const PrefixValidator = {
130
131
  * @param isNumericIdentifier
131
132
  * If true, the prefix is from a numeric identifier and its character set will be validated by the caller.
132
133
  *
133
- * @param positionOffset
134
- * Position offset within a larger string.
134
+ * @param errorMessageParseKey
135
+ * Parse key for error message when validating GS1 Company Prefix type.
135
136
  */
136
- validate(prefixType: PrefixType, allowUPCCompanyPrefix: boolean, allowGS18Prefix: boolean, prefix: string, isFromIdentifier = false, isNumericIdentifier = false, positionOffset?: number): void {
137
- let baseValidation: PrefixValidation;
137
+ validate(prefixType: PrefixType, allowUPCCompanyPrefix: boolean, allowGS18Prefix: boolean, prefix: string, isFromIdentifier = false, isNumericIdentifier = false, errorMessageParseKey?: ParseKeys): void {
138
+ let validation: PrefixValidation;
138
139
 
139
140
  // Validate the prefix type and determine the prefix validation parameters.
140
141
  switch (prefixType) {
141
142
  case PrefixTypes.GS1CompanyPrefix:
142
143
  if (!prefix.startsWith("0")) {
143
- baseValidation = GS1_COMPANY_PREFIX_VALIDATION;
144
+ validation = GS1_COMPANY_PREFIX_VALIDATION;
144
145
  } else if (!prefix.startsWith("00000")) {
145
146
  if (!allowUPCCompanyPrefix) {
146
- throw new RangeError(i18nextGS1.t("Prefix.gs1CompanyPrefixCantStartWith0"));
147
+ throw new RangeError(i18nextGS1.t(errorMessageParseKey ?? "Prefix.gs1CompanyPrefixCantStartWith0"));
147
148
  }
148
149
 
149
- baseValidation = UPC_COMPANY_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION;
150
+ validation = UPC_COMPANY_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION;
150
151
  } else if (!prefix.startsWith("000000")) {
151
152
  if (!allowGS18Prefix) {
152
- throw new RangeError(i18nextGS1.t("Prefix.gs1CompanyPrefixCantStartWith00000"));
153
+ throw new RangeError(i18nextGS1.t(errorMessageParseKey ?? "Prefix.gs1CompanyPrefixCantStartWith00000"));
153
154
  }
154
155
 
155
- baseValidation = GS1_8_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION;
156
+ validation = GS1_8_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION;
156
157
  } else {
157
- throw new RangeError(i18nextGS1.t("Prefix.gs1CompanyPrefixCantStartWith000000"));
158
+ throw new RangeError(i18nextGS1.t(errorMessageParseKey ?? "Prefix.gs1CompanyPrefixCantStartWith000000"));
158
159
  }
159
160
  break;
160
161
 
@@ -163,7 +164,7 @@ export const PrefixValidator = {
163
164
  throw new RangeError(i18nextGS1.t("Prefix.upcCompanyPrefixCantStartWith0000"));
164
165
  }
165
166
 
166
- baseValidation = UPC_COMPANY_PREFIX_VALIDATION;
167
+ validation = UPC_COMPANY_PREFIX_VALIDATION;
167
168
  break;
168
169
 
169
170
  case PrefixTypes.GS18Prefix:
@@ -171,21 +172,16 @@ export const PrefixValidator = {
171
172
  throw new RangeError(i18nextGS1.t("Prefix.gs18PrefixCantStartWith0"));
172
173
  }
173
174
 
174
- baseValidation = GS1_8_PREFIX_VALIDATION;
175
+ validation = GS1_8_PREFIX_VALIDATION;
175
176
  break;
176
177
  }
177
178
 
178
- const mergedValidation: PrefixValidation = {
179
- ...baseValidation,
180
- positionOffset
181
- };
182
-
183
- // If from key and numeric, key validation will take care of character set validation.
179
+ // If from identifier and numeric, identifier validation will take care of character set validation.
184
180
  if (!isFromIdentifier) {
185
- NUMERIC_CREATOR.validate(prefix, mergedValidation);
181
+ NUMERIC_CREATOR.validate(prefix, validation);
186
182
  } else if (!isNumericIdentifier) {
187
183
  // Validate only the minimum length, allowing at least one character for the (possibly non-numeric) reference.
188
- NUMERIC_CREATOR.validate(prefix.substring(0, Math.min(mergedValidation.minimumLength, prefix.length - 1)), mergedValidation);
184
+ NUMERIC_CREATOR.validate(prefix.substring(0, Math.min(validation.minimumLength, prefix.length - 1)), validation);
189
185
  }
190
186
  },
191
187
 
@@ -214,11 +210,11 @@ export const PrefixValidator = {
214
210
  break;
215
211
 
216
212
  case PrefixTypes.UPCCompanyPrefix:
217
- gs1CompanyPrefix = "0" + prefix;
213
+ gs1CompanyPrefix = `0${prefix}`;
218
214
  break;
219
215
 
220
216
  case PrefixTypes.GS18Prefix:
221
- gs1CompanyPrefix = "00000" + prefix;
217
+ gs1CompanyPrefix = `00000${prefix}`;
222
218
  break;
223
219
  }
224
220