@aidc-toolkit/gs1 0.0.1

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.
package/src/idkey.ts ADDED
@@ -0,0 +1,2370 @@
1
+ import {
2
+ CharacterSetCreator,
3
+ type CharacterSetValidation,
4
+ type CharacterSetValidator,
5
+ Exclusion, IterationHelper, type IterationSource, NUMERIC_CREATOR,
6
+ RegExpValidator,
7
+ type StringValidation,
8
+ type StringValidator
9
+ } from "@aidc-toolkit/utility";
10
+ import { Mixin } from "ts-mixer";
11
+ import { AI39_CREATOR, AI82_CREATOR } from "./character_set.js";
12
+ import {
13
+ checkCharacterPair,
14
+ checkDigit,
15
+ checkDigitSum,
16
+ hasValidCheckCharacterPair,
17
+ hasValidCheckDigit
18
+ } from "./check.js";
19
+ import i18next, { gs1NS } from "./locale/i18n.js";
20
+
21
+ /**
22
+ * Identification key type.
23
+ */
24
+ export enum IdentificationKeyType {
25
+ /**
26
+ * Global Trade Item Number.
27
+ */
28
+ GTIN = "GTIN",
29
+
30
+ /**
31
+ * Global Location Number.
32
+ */
33
+ GLN = "GLN",
34
+
35
+ /**
36
+ * Serial Shipping Container Code.
37
+ */
38
+ SSCC = "SSCC",
39
+
40
+ /**
41
+ * Global Returnable Asset Identifier.
42
+ */
43
+ GRAI = "GRAI",
44
+
45
+ /**
46
+ * Global Individual Asset Identifier.
47
+ */
48
+ GIAI = "GIAI",
49
+
50
+ /**
51
+ * Global Service Relation Number.
52
+ */
53
+ GSRN = "GSRN",
54
+
55
+ /**
56
+ * Global Document Type Identifier.
57
+ */
58
+ GDTI = "GDTI",
59
+
60
+ /**
61
+ * Global Identification Number for Consignment.
62
+ */
63
+ GINC = "GINC",
64
+
65
+ /**
66
+ * Global Shipment Identification Number.
67
+ */
68
+ GSIN = "GSIN",
69
+
70
+ /**
71
+ * Global Coupon Number.
72
+ */
73
+ GCN = "GCN",
74
+
75
+ /**
76
+ * Component/Part Identifier.
77
+ */
78
+ CPID = "CPID",
79
+
80
+ /**
81
+ * Global Model Number.
82
+ */
83
+ GMN = "GMN"
84
+ }
85
+
86
+ /**
87
+ * Prefix type.
88
+ */
89
+ export enum PrefixType {
90
+ /**
91
+ * GS1 Company Prefix.
92
+ */
93
+ GS1CompanyPrefix,
94
+
95
+ /**
96
+ * U.P.C. Company Prefix.
97
+ */
98
+ UPCCompanyPrefix,
99
+
100
+ /**
101
+ * GS1-8 Prefix.
102
+ */
103
+ GS18Prefix
104
+ }
105
+
106
+ /**
107
+ * Character set supported by the reference portion of an identification key or the serial component of a numeric
108
+ * identification key.
109
+ */
110
+ export enum CharacterSet {
111
+ /**
112
+ * Numeric.
113
+ */
114
+ Numeric,
115
+
116
+ /**
117
+ * GS1 AI encodable character set 82.
118
+ */
119
+ AI82,
120
+
121
+ /**
122
+ * GS1 AI encodable character set 39.
123
+ */
124
+ AI39
125
+ }
126
+
127
+ /**
128
+ * Identification key validation parameters.
129
+ */
130
+ export interface IdentificationKeyValidation extends StringValidation {
131
+ /**
132
+ * Position offset within a larger string. Strings are sometimes composed of multiple substrings; this parameter
133
+ * ensures that the exception notes the proper position in the string.
134
+ */
135
+ positionOffset?: number | undefined;
136
+ }
137
+
138
+ /**
139
+ * Identification key validator. Validates an identification key against its definition in section 3 of the {@link
140
+ * https://www.gs1.org/genspecs | GS1 General Specifications}.
141
+ */
142
+ export interface IdentificationKeyValidator extends StringValidator {
143
+ /**
144
+ * Get the identification key type. Per the GS1 General Specifications, the identification key type determines
145
+ * the remaining properties.
146
+ */
147
+ get identificationKeyType(): IdentificationKeyType;
148
+
149
+ /**
150
+ * Get the prefix type supported by the identification key type. For all identification key types except the GTIN,
151
+ * this is {@linkcode PrefixType.GS1CompanyPrefix}. For the GTIN, the prefix type determines the length.
152
+ */
153
+ get prefixType(): PrefixType;
154
+
155
+ /**
156
+ * Get the length. For numeric identification key types, the length is fixed; for alphanumeric identification key
157
+ * types, the length is the maximum.
158
+ */
159
+ get length(): number;
160
+
161
+ /**
162
+ * Get the reference character set.
163
+ */
164
+ get referenceCharacterSet(): CharacterSet;
165
+
166
+ /**
167
+ * Get the reference validator.
168
+ */
169
+ get referenceValidator(): CharacterSetValidator;
170
+
171
+ /**
172
+ * Validate an identification key and throw an exception if validation fails.
173
+ *
174
+ * @param identificationKey
175
+ * Identification key.
176
+ *
177
+ * @param validation
178
+ * Identification key validation parameters.
179
+ */
180
+ validate: (identificationKey: string, validation?: IdentificationKeyValidation) => void;
181
+ }
182
+
183
+ /**
184
+ * Abstract identification key validator. Implements common functionality for an identification key validator.
185
+ */
186
+ abstract class AbstractIdentificationKeyValidator implements IdentificationKeyValidator {
187
+ /**
188
+ * Identification key type.
189
+ */
190
+ private readonly _identificationKeyType: IdentificationKeyType;
191
+
192
+ /**
193
+ * Prefix type.
194
+ */
195
+ private readonly _prefixType: PrefixType;
196
+
197
+ /**
198
+ * Length.
199
+ */
200
+ private readonly _length: number;
201
+
202
+ /**
203
+ * Reference character set.
204
+ */
205
+ private readonly _referenceCharacterSet: CharacterSet;
206
+
207
+ /**
208
+ * Character set validator.
209
+ */
210
+ private readonly _referenceValidator: CharacterSetValidator;
211
+
212
+ /**
213
+ * Get the character set validator for a character set.
214
+ *
215
+ * @param characterSet
216
+ * Character set.
217
+ *
218
+ * @returns
219
+ * Character set validator.
220
+ */
221
+ protected static validatorFor(characterSet: CharacterSet): CharacterSetValidator {
222
+ let characterSetValidator: CharacterSetValidator;
223
+
224
+ switch (characterSet) {
225
+ case CharacterSet.Numeric:
226
+ characterSetValidator = NUMERIC_CREATOR;
227
+ break;
228
+
229
+ case CharacterSet.AI82:
230
+ characterSetValidator = AI82_CREATOR;
231
+ break;
232
+
233
+ case CharacterSet.AI39:
234
+ characterSetValidator = AI39_CREATOR;
235
+ break;
236
+ }
237
+
238
+ return characterSetValidator;
239
+ }
240
+
241
+ /**
242
+ * Constructor.
243
+ *
244
+ * @param identificationKeyType
245
+ * Identification key type.
246
+ *
247
+ * @param prefixType
248
+ * Prefix type.
249
+ *
250
+ * @param length
251
+ * Length.
252
+ *
253
+ * @param referenceCharacterSet
254
+ * Reference character set.
255
+ */
256
+ protected constructor(identificationKeyType: IdentificationKeyType, prefixType: PrefixType, length: number, referenceCharacterSet: CharacterSet) {
257
+ this._identificationKeyType = identificationKeyType;
258
+ this._prefixType = prefixType;
259
+ this._length = length;
260
+ this._referenceCharacterSet = referenceCharacterSet;
261
+ this._referenceValidator = AbstractIdentificationKeyValidator.validatorFor(referenceCharacterSet);
262
+ }
263
+
264
+ get identificationKeyType(): IdentificationKeyType {
265
+ return this._identificationKeyType;
266
+ }
267
+
268
+ get prefixType(): PrefixType {
269
+ return this._prefixType;
270
+ }
271
+
272
+ get length(): number {
273
+ return this._length;
274
+ }
275
+
276
+ get referenceCharacterSet(): CharacterSet {
277
+ return this._referenceCharacterSet;
278
+ }
279
+
280
+ get referenceValidator(): CharacterSetValidator {
281
+ return this._referenceValidator;
282
+ }
283
+
284
+ /**
285
+ * Pad an identification key on the left with zeroes for validation purposes. This is done to align an
286
+ * identification key with a position offset for any error message that may be thrown by the reference validator.
287
+ *
288
+ * @param identificationKey
289
+ * Identification key.
290
+ *
291
+ * @param validation
292
+ * Identification key validation parameters.
293
+ *
294
+ * @returns
295
+ * Padded identification key.
296
+ */
297
+ protected static padIdentificationKey(identificationKey: string, validation: IdentificationKeyValidation | undefined): string {
298
+ // Identification key is returned as is if position offset is undefined.
299
+ return validation?.positionOffset === undefined ? identificationKey : "0".repeat(validation.positionOffset).concat(identificationKey);
300
+ }
301
+
302
+ /**
303
+ * Validate the prefix within an identification key.
304
+ *
305
+ * @param partialIdentificationKey
306
+ * Partial identification key.
307
+ *
308
+ * @param positionOffset
309
+ * Position offset within a larger string.
310
+ */
311
+ protected validatePrefix(partialIdentificationKey: string, positionOffset?: number): void {
312
+ // Delegate to prefix manager with support for U.P.C. Company Prefix but not GS1-8 Prefix.
313
+ PrefixManager.validatePrefix(this.prefixType, true, false, partialIdentificationKey, true, this.referenceCharacterSet === CharacterSet.Numeric, positionOffset);
314
+ }
315
+
316
+ abstract validate(identificationKey: string, validation?: IdentificationKeyValidation): void;
317
+ }
318
+
319
+ /**
320
+ * Leader type.
321
+ */
322
+ export enum LeaderType {
323
+ /**
324
+ * No leader.
325
+ */
326
+ None,
327
+
328
+ /**
329
+ * Indicator digit (GTIN only).
330
+ */
331
+ IndicatorDigit,
332
+
333
+ /**
334
+ * Extension digit (SSCC only).
335
+ */
336
+ ExtensionDigit
337
+ }
338
+
339
+ /**
340
+ * Numeric identification key validator. Validates a numeric identification key.
341
+ */
342
+ export interface NumericIdentificationKeyValidator extends IdentificationKeyValidator {
343
+ /**
344
+ * Get the leader type.
345
+ */
346
+ get leaderType(): LeaderType;
347
+ }
348
+
349
+ /**
350
+ * Abstract numeric identification key validator. Implements common functionality for a numeric identification key
351
+ * validator.
352
+ */
353
+ abstract class AbstractNumericIdentificationKeyValidator extends AbstractIdentificationKeyValidator implements NumericIdentificationKeyValidator {
354
+ /**
355
+ * Leader type.
356
+ */
357
+ private readonly _leaderType: LeaderType;
358
+
359
+ /**
360
+ * Prefix position, determined by the leader type.
361
+ */
362
+ private readonly _prefixPosition: number;
363
+
364
+ /**
365
+ * Constructor.
366
+ *
367
+ * @param identificationKeyType
368
+ * Identification key type.
369
+ *
370
+ * @param prefixType
371
+ * Prefix type.
372
+ *
373
+ * @param length
374
+ * Length.
375
+ *
376
+ * @param leaderType
377
+ * Leader type.
378
+ */
379
+ protected constructor(identificationKeyType: IdentificationKeyType, prefixType: PrefixType, length: number, leaderType: LeaderType) {
380
+ super(identificationKeyType, prefixType, length, CharacterSet.Numeric);
381
+
382
+ this._leaderType = leaderType;
383
+ this._prefixPosition = Number(this.leaderType === LeaderType.ExtensionDigit);
384
+ }
385
+
386
+ get leaderType(): LeaderType {
387
+ return this._leaderType;
388
+ }
389
+
390
+ validate(identificationKey: string, validation?: IdentificationKeyValidation): void {
391
+ // Validate the prefix, with care taken for its position within the identification key.
392
+ if (this._prefixPosition === 0) {
393
+ super.validatePrefix(identificationKey, validation?.positionOffset);
394
+ } else {
395
+ super.validatePrefix(identificationKey.substring(this._prefixPosition), validation?.positionOffset === undefined ? this._prefixPosition : validation.positionOffset + this._prefixPosition);
396
+ }
397
+
398
+ // Validate the length.
399
+ if (identificationKey.length !== this.length) {
400
+ throw new RangeError(i18next.t("IdentificationKey.identificationKeyTypeLength", {
401
+ ns: gs1NS,
402
+ identificationKeyType: this.identificationKeyType,
403
+ length: this.length
404
+ }));
405
+ }
406
+
407
+ // Validating the check digit will also validate the characters.
408
+ if (!hasValidCheckDigit(AbstractIdentificationKeyValidator.padIdentificationKey(identificationKey, validation))) {
409
+ throw new RangeError(i18next.t("IdentificationKey.invalidCheckDigit", {
410
+ ns: gs1NS
411
+ }));
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * GTIN type. The numeric values of this enumeration are equal to the lengths of the GTIN types.
418
+ */
419
+ export enum GTINType {
420
+ /**
421
+ * GTIN-13.
422
+ */
423
+ GTIN13 = 13,
424
+
425
+ /**
426
+ * GTIN-12.
427
+ */
428
+ GTIN12 = 12,
429
+
430
+ /**
431
+ * GTIN-8.
432
+ */
433
+ GTIN8 = 8,
434
+
435
+ /**
436
+ * GTIN-14.
437
+ */
438
+ GTIN14 = 14
439
+ }
440
+
441
+ /**
442
+ * Level at which GTIN is to be validated.
443
+ */
444
+ export enum GTINLevel {
445
+ /**
446
+ * Any level (level is ignored).
447
+ */
448
+ Any,
449
+
450
+ /**
451
+ * Retail consumer trade item level, supporting GTIN-13, GTIN-12 (optionally zero-suppressed), and GTIN-8.
452
+ */
453
+ RetailConsumer,
454
+
455
+ /**
456
+ * Other than retail consumer trade item level, supporting GTIN-13, GTIN-12 (not zero-suppressed), and GTIN-14.
457
+ */
458
+ OtherThanRetailConsumer
459
+ }
460
+
461
+ /**
462
+ * GTIN validator.
463
+ */
464
+ export class GTINValidator extends AbstractNumericIdentificationKeyValidator {
465
+ /**
466
+ * Zero-suppressed GTIN-12 validation parameters.
467
+ */
468
+ private static readonly ZERO_SUPPRESSED_GTIN12_VALIDATION: CharacterSetValidation = {
469
+ minimumLength: 8,
470
+ maximumLength: 8
471
+ };
472
+
473
+ /**
474
+ * Constructor.
475
+ *
476
+ * @param gtinType
477
+ * GTIN type.
478
+ */
479
+ constructor(gtinType: GTINType) {
480
+ let prefixType: PrefixType;
481
+
482
+ // Determine the prefix type based on the GTIN type.
483
+ switch (gtinType) {
484
+ case GTINType.GTIN13:
485
+ prefixType = PrefixType.GS1CompanyPrefix;
486
+ break;
487
+
488
+ case GTINType.GTIN12:
489
+ prefixType = PrefixType.UPCCompanyPrefix;
490
+ break;
491
+
492
+ case GTINType.GTIN8:
493
+ prefixType = PrefixType.GS18Prefix;
494
+ break;
495
+
496
+ default:
497
+ // Should never get here.
498
+ throw new Error("Not supported");
499
+ }
500
+
501
+ super(IdentificationKeyType.GTIN, prefixType, gtinType, LeaderType.IndicatorDigit);
502
+ }
503
+
504
+ get gtinType(): GTINType {
505
+ // Length maps to GTIN type enumeration.
506
+ return this.length as GTINType;
507
+ }
508
+
509
+ protected override validatePrefix(partialIdentificationKey: string, positionOffset?: number): void {
510
+ // Delegate to prefix manager requiring exact match for prefix type.
511
+ PrefixManager.validatePrefix(this.prefixType, false, false, partialIdentificationKey, true, true, positionOffset);
512
+ }
513
+
514
+ /**
515
+ * Zero expand a zero-suppressed GTIN-12.
516
+ *
517
+ * @param zeroSuppressedGTIN12
518
+ * Zero-suppressed GTIN-12.
519
+ *
520
+ * @returns
521
+ * GTIN-12.
522
+ *
523
+ * @throws RangeError
524
+ */
525
+ static zeroExpand(zeroSuppressedGTIN12: string): string {
526
+ NUMERIC_CREATOR.validate(zeroSuppressedGTIN12, GTINValidator.ZERO_SUPPRESSED_GTIN12_VALIDATION);
527
+
528
+ // Convert to individual digits.
529
+ const d = Array.from(zeroSuppressedGTIN12);
530
+
531
+ let gtin12: string | undefined;
532
+
533
+ // Zero-suppressed GTIN-12 always starts with 0.
534
+ if (d[0] === "0") {
535
+ if (d[6] >= "5" && d[5] !== "0") {
536
+ gtin12 = `0${d[1]}${d[2]}${d[3]}${d[4]}${d[5]}0000${d[6]}${d[7]}`;
537
+ } else if (d[6] === "4" && d[4] !== "0") {
538
+ gtin12 = `0${d[1]}${d[2]}${d[3]}${d[4]}00000${d[5]}${d[7]}`;
539
+ } else if (d[6] <= "2") {
540
+ gtin12 = `0${d[1]}${d[2]}${d[6]}0000${d[3]}${d[4]}${d[5]}${d[7]}`;
541
+ } else if (d[6] === "3" && d[3] >= "3") {
542
+ gtin12 = `0${d[1]}${d[2]}${d[3]}00000${d[4]}${d[5]}${d[7]}`;
543
+ }
544
+ }
545
+
546
+ if (gtin12 === undefined) {
547
+ throw new RangeError(i18next.t("IdentificationKey.invalidZeroSuppressedGTIN12", {
548
+ ns: gs1NS
549
+ }));
550
+ }
551
+
552
+ // Make sure that resulting GTIN-12 is valid.
553
+ GTIN12_VALIDATOR.validate(gtin12);
554
+
555
+ return gtin12;
556
+ }
557
+
558
+ /**
559
+ * Validate any GTIN, optionally against a level.
560
+ *
561
+ * @param gtin
562
+ * GTIN.
563
+ *
564
+ * @param gtinLevel
565
+ * Level at which GTIN is to be validated.
566
+ *
567
+ * @throws RangeError
568
+ */
569
+ static validateAny(gtin: string, gtinLevel: GTINLevel = GTINLevel.Any): void {
570
+ // Assume length-validated GTIN is the GTIN (true for all except zero-suppressed GTIN-12).
571
+ let lengthValidatedGTIN = gtin;
572
+
573
+ let gtinLevelRestriction: GTINLevel;
574
+
575
+ switch (gtin.length) {
576
+ case GTINType.GTIN13 as number:
577
+ if (gtin.startsWith("0")) {
578
+ throw new RangeError(i18next.t("IdentificationKey.invalidGTIN13AtRetail", {
579
+ ns: gs1NS
580
+ }));
581
+ }
582
+
583
+ // Validate prefix requiring exact match for prefix type.
584
+ PrefixManager.validatePrefix(PrefixType.GS1CompanyPrefix, false, false, gtin, true, true);
585
+
586
+ gtinLevelRestriction = GTINLevel.Any;
587
+ break;
588
+
589
+ case GTINType.GTIN12 as number:
590
+ // Validate prefix requiring exact match for prefix type.
591
+ PrefixManager.validatePrefix(PrefixType.UPCCompanyPrefix, false, false, gtin, true, true);
592
+
593
+ gtinLevelRestriction = GTINLevel.Any;
594
+ break;
595
+
596
+ case GTINType.GTIN8 as number:
597
+ // Zero-suppressed GTIN-12 always starts with 0.
598
+ if (!gtin.startsWith("0")) {
599
+ // Validate prefix requiring exact match for prefix type.
600
+ PrefixManager.validatePrefix(PrefixType.GS18Prefix, false, false, gtin, true, true);
601
+ } else {
602
+ lengthValidatedGTIN = GTINValidator.zeroExpand(gtin);
603
+ }
604
+
605
+ gtinLevelRestriction = GTINLevel.RetailConsumer;
606
+ break;
607
+
608
+ case GTINType.GTIN14 as number:
609
+ // Validate prefix supporting any prefix type.
610
+ PrefixManager.validatePrefix(PrefixType.GS1CompanyPrefix, true, true, gtin.substring(1), true, true);
611
+
612
+ gtinLevelRestriction = GTINLevel.OtherThanRetailConsumer;
613
+ break;
614
+
615
+ default:
616
+ throw new RangeError(i18next.t("IdentificationKey.invalidGTINLength", {
617
+ ns: gs1NS
618
+ }));
619
+ }
620
+
621
+ // Validating the check digit will also validate the characters.
622
+ if (!hasValidCheckDigit(lengthValidatedGTIN)) {
623
+ throw new RangeError(i18next.t("IdentificationKey.invalidCheckDigit", {
624
+ ns: gs1NS
625
+ }));
626
+ }
627
+
628
+ // Validate against level if required.
629
+ if (gtinLevel !== GTINLevel.Any && gtinLevelRestriction !== GTINLevel.Any && gtinLevelRestriction !== gtinLevel) {
630
+ throw new RangeError(i18next.t(gtinLevel === GTINLevel.RetailConsumer ? "IdentificationKey.invalidGTINAtRetail" : "IdentificationKey.invalidGTINAtOtherThanRetail", {
631
+ ns: gs1NS
632
+ }));
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Validate a GTIN-14.
638
+ *
639
+ * @param gtin14
640
+ * GTIN-14.
641
+ *
642
+ * @throws RangeError
643
+ */
644
+ static validateGTIN14(gtin14: string): void {
645
+ if (gtin14.length !== GTINType.GTIN14 as number) {
646
+ throw new RangeError(i18next.t("IdentificationKey.invalidGTIN14Length", {
647
+ ns: gs1NS
648
+ }));
649
+ }
650
+
651
+ GTINCreator.validateAny(gtin14);
652
+ }
653
+ }
654
+
655
+ /**
656
+ * Non-GTIN numeric identification key validator.
657
+ */
658
+ export class NonGTINNumericIdentificationKeyValidator extends AbstractNumericIdentificationKeyValidator {
659
+ /**
660
+ * Constructor.
661
+ *
662
+ * @param identificationKeyType
663
+ * Identification key type.
664
+ *
665
+ * @param length
666
+ * Length.
667
+ *
668
+ * @param leaderType
669
+ * Leader type.
670
+ */
671
+ constructor(identificationKeyType: IdentificationKeyType, length: number, leaderType: LeaderType = LeaderType.None) {
672
+ super(identificationKeyType, PrefixType.GS1CompanyPrefix, length, leaderType);
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Serializable numeric identification key validator. Validates both serialized and non-serialized forms of
678
+ * numeric identification keys that support serialization.
679
+ */
680
+ export class SerializableNumericIdentificationKeyValidator extends NonGTINNumericIdentificationKeyValidator {
681
+ /**
682
+ * Serial component length.
683
+ */
684
+ private readonly _serialComponentLength: number;
685
+
686
+ /**
687
+ * Serial component character set.
688
+ */
689
+ private readonly _serialComponentCharacterSet: CharacterSet;
690
+
691
+ /**
692
+ * Serial component character set validation parameters.
693
+ */
694
+ private readonly _serialComponentValidation: CharacterSetValidation;
695
+
696
+ /**
697
+ * Serial component validator.
698
+ */
699
+ private readonly _serialComponentValidator: CharacterSetValidator;
700
+
701
+ /**
702
+ * Constructor.
703
+ *
704
+ * @param identificationKeyType
705
+ * Identification key type.
706
+ *
707
+ * @param length
708
+ * Length.
709
+ *
710
+ * @param serialComponentLength
711
+ * Serial component length.
712
+ *
713
+ * @param serialComponentCharacterSet
714
+ * Serial component character set.
715
+ */
716
+ constructor(identificationKeyType: IdentificationKeyType, length: number, serialComponentLength: number, serialComponentCharacterSet: CharacterSet) {
717
+ super(identificationKeyType, length, LeaderType.None);
718
+
719
+ this._serialComponentLength = serialComponentLength;
720
+ this._serialComponentCharacterSet = serialComponentCharacterSet;
721
+
722
+ this._serialComponentValidation = {
723
+ minimumLength: 1,
724
+ maximumLength: serialComponentLength,
725
+ component: () => i18next.t("IdentificationKey.serialComponent", {
726
+ ns: gs1NS
727
+ })
728
+ };
729
+
730
+ this._serialComponentValidator = SerializableNumericIdentificationKeyValidator.validatorFor(serialComponentCharacterSet);
731
+ }
732
+
733
+ /**
734
+ * Get the serial component length.
735
+ */
736
+ get serialComponentLength(): number {
737
+ return this._serialComponentLength;
738
+ }
739
+
740
+ /**
741
+ * Get the serial component character set.
742
+ */
743
+ get serialComponentCharacterSet(): CharacterSet {
744
+ return this._serialComponentCharacterSet;
745
+ }
746
+
747
+ /**
748
+ * Get the serial component validation parameters.
749
+ */
750
+ protected get serialComponentValidation(): CharacterSetValidation {
751
+ return this._serialComponentValidation;
752
+ }
753
+
754
+ /**
755
+ * Get the serial component validator.
756
+ */
757
+ get serialComponentValidator(): CharacterSetValidator {
758
+ return this._serialComponentValidator;
759
+ }
760
+
761
+ override validate(identificationKey: string, validation?: IdentificationKeyValidation): void {
762
+ super.validate(identificationKey.substring(0, this.length), validation);
763
+
764
+ if (identificationKey.length > this.length) {
765
+ this.serialComponentValidator.validate(identificationKey.substring(this.length), this._serialComponentValidation);
766
+ }
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Non-numeric identification key validation parameters.
772
+ */
773
+ export interface NonNumericIdentificationKeyValidation extends IdentificationKeyValidation {
774
+ exclusion?: Exclusion.None | Exclusion.AllNumeric | undefined;
775
+ }
776
+
777
+ /**
778
+ * Non-numeric identification key validator.
779
+ */
780
+ export class NonNumericIdentificationKeyValidator extends AbstractIdentificationKeyValidator {
781
+ /**
782
+ * Validator to ensure that an identification key (minus check character pair) is not all numeric.
783
+ */
784
+ private static readonly NOT_ALL_NUMERIC_VALIDATOR = new class extends RegExpValidator {
785
+ protected override createErrorMessage(_s: string): string {
786
+ return i18next.t("IdentificationKey.referenceCantBeAllNumeric", {
787
+ ns: gs1NS
788
+ });
789
+ }
790
+ }(/\D/);
791
+
792
+ /**
793
+ * True if the identification key requires a check character pair.
794
+ */
795
+ private readonly _requiresCheckCharacterPair: boolean;
796
+
797
+ /**
798
+ * Constructor.
799
+ *
800
+ * @param identificationKeyType
801
+ * Identification key type.
802
+ *
803
+ * @param length
804
+ * Length.
805
+ *
806
+ * @param referenceCharacterSet
807
+ * Reference character set.
808
+ *
809
+ * @param requiresCheckCharacterPair
810
+ * True if the identification key requires a check character pair.
811
+ */
812
+ constructor(identificationKeyType: IdentificationKeyType, length: number, referenceCharacterSet: CharacterSet, requiresCheckCharacterPair = false) {
813
+ super(identificationKeyType, PrefixType.GS1CompanyPrefix, length, referenceCharacterSet);
814
+
815
+ this._requiresCheckCharacterPair = requiresCheckCharacterPair;
816
+ }
817
+
818
+ /**
819
+ * Determine if the identification key requires a check character pair.
820
+ */
821
+ get requiresCheckCharacterPair(): boolean {
822
+ return this._requiresCheckCharacterPair;
823
+ }
824
+
825
+ validate(identificationKey: string, validation?: NonNumericIdentificationKeyValidation): void {
826
+ const partialIdentificationKey = this.requiresCheckCharacterPair ? identificationKey.substring(0, identificationKey.length - 2) : identificationKey;
827
+
828
+ super.validatePrefix(partialIdentificationKey, validation?.positionOffset);
829
+
830
+ if (!this.requiresCheckCharacterPair) {
831
+ this.referenceValidator.validate(identificationKey, {
832
+ maximumLength: this.length,
833
+ positionOffset: validation?.positionOffset
834
+ });
835
+ // Validating the check character pair will also validate the characters.
836
+ } else if (!hasValidCheckCharacterPair(AbstractIdentificationKeyValidator.padIdentificationKey(identificationKey, validation))) {
837
+ throw new RangeError(i18next.t("IdentificationKey.invalidCheckCharacterPair", {
838
+ ns: gs1NS
839
+ }));
840
+ }
841
+
842
+ // Check for all-numeric identification key (minus check character pair) if excluded.
843
+ if (validation?.exclusion === Exclusion.AllNumeric) {
844
+ NonNumericIdentificationKeyValidator.NOT_ALL_NUMERIC_VALIDATOR.validate(partialIdentificationKey);
845
+ }
846
+ }
847
+ }
848
+
849
+ /**
850
+ * GTIN-13 validator.
851
+ */
852
+ export const GTIN13_VALIDATOR = new GTINValidator(GTINType.GTIN13);
853
+
854
+ /**
855
+ * GTIN-12 validator.
856
+ */
857
+ export const GTIN12_VALIDATOR = new GTINValidator(GTINType.GTIN12);
858
+
859
+ /**
860
+ * GTIN-8 validator.
861
+ */
862
+ export const GTIN8_VALIDATOR = new GTINValidator(GTINType.GTIN8);
863
+
864
+ /**
865
+ * GTIN validators indexed by prefix type.
866
+ */
867
+ export const GTIN_VALIDATORS = [
868
+ GTIN13_VALIDATOR, GTIN12_VALIDATOR, GTIN8_VALIDATOR
869
+ ];
870
+
871
+ /**
872
+ * GLN validator.
873
+ */
874
+ export const GLN_VALIDATOR = new NonGTINNumericIdentificationKeyValidator(IdentificationKeyType.GLN, 13);
875
+
876
+ /**
877
+ * SSCC validator.
878
+ */
879
+ export const SSCC_VALIDATOR = new NonGTINNumericIdentificationKeyValidator(IdentificationKeyType.SSCC, 18, LeaderType.ExtensionDigit);
880
+
881
+ /**
882
+ * GRAI validator.
883
+ */
884
+ export const GRAI_VALIDATOR = new SerializableNumericIdentificationKeyValidator(IdentificationKeyType.GRAI, 13, 16, CharacterSet.AI82);
885
+
886
+ /**
887
+ * GIAI validator.
888
+ */
889
+ export const GIAI_VALIDATOR = new NonNumericIdentificationKeyValidator(IdentificationKeyType.GIAI, 30, CharacterSet.AI82);
890
+
891
+ /**
892
+ * GSRN validator.
893
+ */
894
+ export const GSRN_VALIDATOR = new NonGTINNumericIdentificationKeyValidator(IdentificationKeyType.GSRN, 18);
895
+
896
+ /**
897
+ * GDTI validator.
898
+ */
899
+ export const GDTI_VALIDATOR = new SerializableNumericIdentificationKeyValidator(IdentificationKeyType.GDTI, 13, 17, CharacterSet.AI82);
900
+
901
+ /**
902
+ * GINC validator.
903
+ */
904
+ export const GINC_VALIDATOR = new NonNumericIdentificationKeyValidator(IdentificationKeyType.GINC, 30, CharacterSet.AI82);
905
+
906
+ /**
907
+ * GSIN validator.
908
+ */
909
+ export const GSIN_VALIDATOR = new NonGTINNumericIdentificationKeyValidator(IdentificationKeyType.GSIN, 17);
910
+
911
+ /**
912
+ * GCN validator.
913
+ */
914
+ export const GCN_VALIDATOR = new SerializableNumericIdentificationKeyValidator(IdentificationKeyType.GCN, 13, 12, CharacterSet.Numeric);
915
+
916
+ /**
917
+ * CPID validator.
918
+ */
919
+ export const CPID_VALIDATOR = new NonNumericIdentificationKeyValidator(IdentificationKeyType.CPID, 30, CharacterSet.AI39);
920
+
921
+ /**
922
+ * GMN validator.
923
+ */
924
+ export const GMN_VALIDATOR = new NonNumericIdentificationKeyValidator(IdentificationKeyType.GMN, 25, CharacterSet.AI82, true);
925
+
926
+ /**
927
+ * Identification key creator. Creates an identification key based on its definition in section 3 of the {@link
928
+ * https://www.gs1.org/genspecs | GS1 General Specifications}.
929
+ *
930
+ * Keys are created based on a prefix defined in a prefix manager to which the identification key creator is bound.
931
+ */
932
+ export interface IdentificationKeyCreator extends IdentificationKeyValidator {
933
+ /**
934
+ * Get the reference creator.
935
+ */
936
+ get referenceCreator(): CharacterSetCreator;
937
+
938
+ /**
939
+ * Get the prefix manager to which this identification key creator is bound.
940
+ */
941
+ get prefixManager(): PrefixManager;
942
+
943
+ /**
944
+ * Get the prefix, equivalent to calling {@linkcode PrefixManager.prefix | prefixManager.prefix} for a GTIN or
945
+ * {@linkcode PrefixManager.gs1CompanyPrefix | prefixManager.gs1CompanyPrefix} for all other identification key
946
+ * types.
947
+ */
948
+ get prefix(): string;
949
+
950
+ /**
951
+ * Get the reference length.
952
+ */
953
+ get referenceLength(): number;
954
+ }
955
+
956
+ /**
957
+ * Abstract identification key creator. Implements common functionality for an identification key creator, bound to a
958
+ * {@link PrefixManager}.
959
+ */
960
+ abstract class AbstractIdentificationKeyCreator implements IdentificationKeyCreator {
961
+ /**
962
+ * Prefix manager.
963
+ */
964
+ private _prefixManager?: PrefixManager;
965
+
966
+ /**
967
+ * Reference length.
968
+ */
969
+ private _referenceLength?: number;
970
+
971
+ /**
972
+ * Initialize the prefix manager. This method is in lieu of a constructor due to the mixin architecture.
973
+ *
974
+ * @param prefixManager
975
+ * Prefix manager.
976
+ *
977
+ * @param prefix
978
+ * Prefix within prefix manager to use to calculate reference length.
979
+ *
980
+ * @param checkAllowance
981
+ * Number of characters to allow for check digit or check character pair.
982
+ *
983
+ * @throws Error
984
+ */
985
+ protected init(prefixManager: PrefixManager, prefix: string, checkAllowance: number): void {
986
+ // Verify that prefix manager has not already been assigned.
987
+ if (this._prefixManager !== undefined) {
988
+ // Should never get here.
989
+ throw new Error("Not supported");
990
+ }
991
+
992
+ this._prefixManager = prefixManager;
993
+
994
+ // Reference length allows for prefix and optionally check digit or check character pair.
995
+ this._referenceLength = this.length - prefix.length - checkAllowance;
996
+ }
997
+
998
+ abstract get identificationKeyType(): IdentificationKeyType;
999
+
1000
+ abstract get prefixType(): PrefixType;
1001
+
1002
+ abstract get length(): number;
1003
+
1004
+ abstract get referenceCharacterSet(): CharacterSet;
1005
+
1006
+ abstract get referenceValidator(): CharacterSetValidator;
1007
+
1008
+ get referenceCreator(): CharacterSetCreator {
1009
+ return this.referenceValidator as CharacterSetCreator;
1010
+ }
1011
+
1012
+ get prefixManager(): PrefixManager {
1013
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1014
+ return this._prefixManager!;
1015
+ }
1016
+
1017
+ get prefix(): string {
1018
+ return this.prefixManager.gs1CompanyPrefix;
1019
+ }
1020
+
1021
+ get referenceLength(): number {
1022
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1023
+ return this._referenceLength!;
1024
+ }
1025
+
1026
+ abstract validate(identificationKey: string, validation?: IdentificationKeyValidation): void;
1027
+ }
1028
+
1029
+ /**
1030
+ * Numeric identification key creator. Creates one or many numeric identification keys.
1031
+ */
1032
+ export interface NumericIdentificationKeyCreator extends NumericIdentificationKeyValidator, IdentificationKeyCreator {
1033
+ /**
1034
+ * Get the capacity (`10**referenceLength`).
1035
+ */
1036
+ get capacity(): number;
1037
+
1038
+ /**
1039
+ * Create an identification key with a reference based on a numeric value. The value is converted to a reference of
1040
+ * the appropriate length using {@linkcode NUMERIC_CREATOR}.
1041
+ *
1042
+ * @param value
1043
+ * Numeric value.
1044
+ *
1045
+ * @param sparse
1046
+ * If true, the value is mapped to a sparse sequence resistant to discovery. Default is false.
1047
+ *
1048
+ * @returns
1049
+ * Identification key.
1050
+ */
1051
+ create: (value: number, sparse?: boolean) => string;
1052
+
1053
+ /**
1054
+ * Create a sequence of identification keys with references based on a sequence of numeric values. The values are
1055
+ * converted to references of the appropriate length using {@linkcode NUMERIC_CREATOR}. References are created with
1056
+ * values from `startValue` to `startValue + count - 1`.
1057
+ *
1058
+ * The implementation uses {@link CharacterSetCreator.createSequence}, so the values are created only as needed.
1059
+ *
1060
+ * @param startValue
1061
+ * Start numeric value.
1062
+ *
1063
+ * @param count
1064
+ * Count of identification keys to create.
1065
+ *
1066
+ * @param sparse
1067
+ * If true, the values are mapped to a sparse sequence resistant to discovery. Default is false.
1068
+ *
1069
+ * @returns
1070
+ * Iterable iterator over created identification keys.
1071
+ */
1072
+ createSequence: (startValue: number, count: number, sparse?: boolean) => IterableIterator<string>;
1073
+
1074
+ /**
1075
+ * Create multiple identification keys with references based on numeric values. The values are converted to
1076
+ * references of the appropriate length using {@linkcode NUMERIC_CREATOR}.
1077
+ *
1078
+ * The implementation uses {@link CharacterSetCreator.createMultiple}, so the values are created only as needed.
1079
+ *
1080
+ * @param valuesSource
1081
+ * Source of values.
1082
+ *
1083
+ * @param sparse
1084
+ * If true, the values are mapped to a sparse sequence resistant to discovery. Default is false.
1085
+ *
1086
+ * @returns
1087
+ * Iterable iterator over created identification keys.
1088
+ */
1089
+ createMultiple: (valuesSource: IterationSource<number>, sparse?: boolean) => IterableIterator<string>;
1090
+
1091
+ /**
1092
+ * Create all identification keys for the prefix from `0` to `capacity - 1`.
1093
+ *
1094
+ * The implementation creates the strings as needed using an internal generator function, so the values are created
1095
+ * only as needed.
1096
+ *
1097
+ * @returns
1098
+ * Iterable iterator over created identification keys.
1099
+ */
1100
+ createAll: () => IterableIterator<string>;
1101
+ }
1102
+
1103
+ /**
1104
+ * Abstract numeric identification key creator. Implements common functionality for a numeric identification key creator.
1105
+ */
1106
+ abstract class AbstractNumericIdentificationKeyCreator extends AbstractIdentificationKeyCreator implements NumericIdentificationKeyCreator {
1107
+ /**
1108
+ * Capacity.
1109
+ */
1110
+ private _capacity?: number;
1111
+
1112
+ /**
1113
+ * Tweak for sparse creation.
1114
+ */
1115
+ private _tweak = 0n;
1116
+
1117
+ /**
1118
+ * Initialize the prefix manager. This method is in lieu of a constructor due to the mixin architecture.
1119
+ *
1120
+ * @param prefixManager
1121
+ * Prefix manager.
1122
+ *
1123
+ * @param prefix
1124
+ * Prefix within prefix manager to use to calculate reference length.
1125
+ *
1126
+ * @throws Error
1127
+ */
1128
+ protected override init(prefixManager: PrefixManager, prefix: string): void {
1129
+ super.init(prefixManager, prefix, 1);
1130
+
1131
+ // Capacity is always in number range.
1132
+ this._capacity = Number(CharacterSetCreator.powerOf10(this.referenceLength));
1133
+ }
1134
+
1135
+ abstract get leaderType(): LeaderType;
1136
+
1137
+ get capacity(): number {
1138
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1139
+ return this._capacity!;
1140
+ }
1141
+
1142
+ /**
1143
+ * Get the tweak for sparse creation.
1144
+ */
1145
+ get tweak(): bigint {
1146
+ return this._tweak;
1147
+ }
1148
+
1149
+ /**
1150
+ * Set the tweak for sparse creation.
1151
+ */
1152
+ set tweak(value: bigint) {
1153
+ this._tweak = value;
1154
+ }
1155
+
1156
+ /**
1157
+ * Build an identification key from a reference by merging it with the prefix and adding the check digit.
1158
+ *
1159
+ * @param reference
1160
+ * Identification key reference.
1161
+ *
1162
+ * @returns
1163
+ * Identification key.
1164
+ */
1165
+ private buildIdentificationKey(reference: string): string {
1166
+ const partialIdentificationKey = this.leaderType === LeaderType.ExtensionDigit ? reference.substring(0, 1) + this.prefix + reference.substring(1) : this.prefix + reference;
1167
+
1168
+ return partialIdentificationKey + checkDigit(partialIdentificationKey);
1169
+ }
1170
+
1171
+ create(value: number, sparse = false): string {
1172
+ return NUMERIC_CREATOR.create(this.referenceLength, value, Exclusion.None, sparse ? this.tweak : undefined, reference => this.buildIdentificationKey(reference));
1173
+ }
1174
+
1175
+ createSequence(startValue: number, count: number, sparse = false): IterableIterator<string> {
1176
+ return NUMERIC_CREATOR.createSequence(this.referenceLength, startValue, count, Exclusion.None, sparse ? this.tweak : undefined, reference => this.buildIdentificationKey(reference));
1177
+ }
1178
+
1179
+ createMultiple(valuesSource: IterationSource<number>, sparse = false): IterableIterator<string> {
1180
+ return NUMERIC_CREATOR.createMultiple(this.referenceLength, valuesSource, Exclusion.None, sparse ? this.tweak : undefined, reference => this.buildIdentificationKey(reference));
1181
+ }
1182
+
1183
+ /**
1184
+ * Create all identification keys from a partial identification key. Call is recursive until remaining reference
1185
+ * length is 0.
1186
+ *
1187
+ * @param partialIdentificationKey
1188
+ * Partial identification key. Initial value is `this.prefix`.
1189
+ *
1190
+ * @param remainingReferenceLength
1191
+ * Remaining reference length. Initial value is `this.referenceLength`.
1192
+ *
1193
+ * @param extensionWeight
1194
+ * If this value is not zero, the identification key has an extension digit, this call is setting it, and this value
1195
+ * is applied to the calculation of the check digit.
1196
+ *
1197
+ * @param weight
1198
+ * If the extension weight is zero, this value is applied to the calculation of the check digit.
1199
+ *
1200
+ * @param partialCheckDigitSum
1201
+ * Partial check digit sum for the partial identification key.
1202
+ *
1203
+ * @yields
1204
+ * Identification key.
1205
+ */
1206
+ private static * createAllPartial(partialIdentificationKey: string, remainingReferenceLength: number, extensionWeight: number, weight: number, partialCheckDigitSum: number): Generator<string> {
1207
+ if (remainingReferenceLength === 0) {
1208
+ // Finalize check digit calculation and append.
1209
+ yield partialIdentificationKey + NUMERIC_CREATOR.character(9 - (partialCheckDigitSum + 9) % 10);
1210
+ } else {
1211
+ const nextRemainingReferenceLength = remainingReferenceLength - 1;
1212
+
1213
+ let nextPartialCheckDigitSum = partialCheckDigitSum;
1214
+
1215
+ if (extensionWeight !== 0) {
1216
+ // Apply every digit to the extension digit.
1217
+ for (const c of NUMERIC_CREATOR.characterSet) {
1218
+ yield * AbstractNumericIdentificationKeyCreator.createAllPartial(c + partialIdentificationKey, nextRemainingReferenceLength, 0, weight, nextPartialCheckDigitSum);
1219
+
1220
+ nextPartialCheckDigitSum += extensionWeight;
1221
+ }
1222
+ } else {
1223
+ const nextWeight = 4 - weight;
1224
+
1225
+ // Apply every digit to the current character in the identification key.
1226
+ for (const c of NUMERIC_CREATOR.characterSet) {
1227
+ yield * AbstractNumericIdentificationKeyCreator.createAllPartial(partialIdentificationKey + c, nextRemainingReferenceLength, 0, nextWeight, nextPartialCheckDigitSum);
1228
+
1229
+ nextPartialCheckDigitSum += weight;
1230
+ }
1231
+ }
1232
+ }
1233
+ }
1234
+
1235
+ createAll(): IterableIterator<string> {
1236
+ const hasExtensionDigit = this.leaderType === LeaderType.ExtensionDigit;
1237
+ const prefix = this.prefix;
1238
+ const referenceLength = this.referenceLength;
1239
+
1240
+ // Start weight is for reference excluding extension digit, which has its weight calculated separately.
1241
+ const startWeight = 3 - 2 * ((referenceLength + 1 - Number(hasExtensionDigit)) % 2);
1242
+
1243
+ return AbstractNumericIdentificationKeyCreator.createAllPartial(prefix, referenceLength, hasExtensionDigit ? 3 - 2 * this.length % 2 : 0, startWeight, checkDigitSum(startWeight === 3, prefix));
1244
+ }
1245
+ }
1246
+
1247
+ /**
1248
+ * GTIN creator. Applicable to GTIN-13, GTIN-12, and GTIN-8 types; no applicable to GTIN-14 type.
1249
+ */
1250
+ export class GTINCreator extends Mixin(GTINValidator, AbstractNumericIdentificationKeyCreator) {
1251
+ /**
1252
+ * Validation parameters for required indicator digit.
1253
+ */
1254
+ private static readonly REQUIRED_INDICATOR_DIGIT_VALIDATION: CharacterSetValidation = {
1255
+ minimumLength: 1,
1256
+ maximumLength: 1,
1257
+ component: () => i18next.t("IdentificationKey.indicatorDigit", {
1258
+ ns: gs1NS
1259
+ })
1260
+ };
1261
+
1262
+ /**
1263
+ * Validation parameters for optional indicator digit.
1264
+ */
1265
+ private static readonly OPTIONAL_INDICATOR_DIGIT_VALIDATION: CharacterSetValidation = {
1266
+ minimumLength: 0,
1267
+ maximumLength: 1,
1268
+ component: () => i18next.t("IdentificationKey.indicatorDigit", {
1269
+ ns: gs1NS
1270
+ })
1271
+ };
1272
+
1273
+ /**
1274
+ * Constructor. Called internally by {@link PrefixManager.gtinCreator}; should not be called by other code.
1275
+ *
1276
+ * @param prefixManager
1277
+ * Prefix manager.
1278
+ *
1279
+ * @param gtinType
1280
+ * GTIN type.
1281
+ */
1282
+ constructor(prefixManager: PrefixManager, gtinType: GTINType) {
1283
+ super(gtinType);
1284
+
1285
+ this.init(prefixManager, prefixManager.prefix);
1286
+ }
1287
+
1288
+ override get prefix(): string {
1289
+ return this.prefixManager.prefix;
1290
+ }
1291
+
1292
+ /**
1293
+ * Build a GTIN-14 from an indicator digit, the prefix, and a reference.
1294
+ *
1295
+ * @param indicatorDigit
1296
+ * Indicator digit.
1297
+ *
1298
+ * @param reference
1299
+ * Reference.
1300
+ *
1301
+ * @returns
1302
+ * GTIN-14.
1303
+ */
1304
+ private buildGTIN14(indicatorDigit: string, reference: string): string {
1305
+ const partialIdentificationKey = indicatorDigit + this.prefixManager.gs1CompanyPrefix + reference;
1306
+
1307
+ return partialIdentificationKey + checkDigit(partialIdentificationKey);
1308
+ }
1309
+
1310
+ /**
1311
+ * Create a GTIN-14 with an indicator digit and a reference based on a numeric value. The value is converted to a
1312
+ * reference of the appropriate length using {@linkcode NUMERIC_CREATOR}.
1313
+ *
1314
+ * @param indicatorDigit
1315
+ * Indicator digit.
1316
+ *
1317
+ * @param value
1318
+ * Numeric value.
1319
+ *
1320
+ * @param sparse
1321
+ * If true, the value is mapped to a sparse sequence resistant to discovery. Default is false.
1322
+ *
1323
+ * @returns
1324
+ * GTIN-14.
1325
+ */
1326
+ createGTIN14(indicatorDigit: string, value: number, sparse = false): string {
1327
+ NUMERIC_CREATOR.validate(indicatorDigit, GTINCreator.REQUIRED_INDICATOR_DIGIT_VALIDATION);
1328
+
1329
+ return NUMERIC_CREATOR.create(GTINType.GTIN13 - this.prefixManager.gs1CompanyPrefix.length - 1, value, Exclusion.None, sparse ? this.tweak : undefined, reference => this.buildGTIN14(indicatorDigit, reference));
1330
+ }
1331
+
1332
+ /**
1333
+ * Create a sequence of GTIN-14s with an indicator digit and references based on a sequence of numeric values.
1334
+ * The values are converted to references of the appropriate length using {@linkcode NUMERIC_CREATOR}. References
1335
+ * are created with values from `startValue` to `startValue + count - 1`.
1336
+ *
1337
+ * The implementation uses {@link CharacterSetCreator.createSequence}, so the values are created only as needed.
1338
+ *
1339
+ * @param indicatorDigit
1340
+ * Indicator digit.
1341
+ *
1342
+ * @param startValue
1343
+ * Start numeric value.
1344
+ *
1345
+ * @param count
1346
+ * Count of identification keys to create.
1347
+ *
1348
+ * @param sparse
1349
+ * If true, the values are mapped to a sparse sequence resistant to discovery. Default is false.
1350
+ *
1351
+ * @returns
1352
+ * Iterable iterator over created GTIN-14s.
1353
+ */
1354
+ createGTIN14Sequence(indicatorDigit: string, startValue: number, count: number, sparse = false): IterableIterator<string> {
1355
+ NUMERIC_CREATOR.validate(indicatorDigit, GTINCreator.REQUIRED_INDICATOR_DIGIT_VALIDATION);
1356
+
1357
+ return NUMERIC_CREATOR.createSequence(GTINType.GTIN13 - this.prefixManager.gs1CompanyPrefix.length - 1, startValue, count, Exclusion.None, sparse ? this.tweak : undefined, reference => this.buildGTIN14(indicatorDigit, reference));
1358
+ }
1359
+
1360
+ /**
1361
+ * Create multiple GTIN-14s with an indicator digit and references based on numeric values. The values are converted
1362
+ * to references of the appropriate length using {@linkcode NUMERIC_CREATOR}.
1363
+ *
1364
+ * The implementation uses {@link CharacterSetCreator.createMultiple}, so the values are created only as needed.
1365
+ *
1366
+ * @param indicatorDigit
1367
+ * Indicator digit.
1368
+ *
1369
+ * @param valuesSource
1370
+ * Source of values.
1371
+ *
1372
+ * @param sparse
1373
+ * If true, the values are mapped to a sparse sequence resistant to discovery. Default is false.
1374
+ *
1375
+ * @returns
1376
+ * Iterable iterator over created GTIN-14s.
1377
+ */
1378
+ createGTIN14Multiple(indicatorDigit: string, valuesSource: IterationSource<number>, sparse = false): IterableIterator<string> {
1379
+ NUMERIC_CREATOR.validate(indicatorDigit, GTINCreator.REQUIRED_INDICATOR_DIGIT_VALIDATION);
1380
+
1381
+ return NUMERIC_CREATOR.createMultiple(GTINType.GTIN13 - this.prefixManager.gs1CompanyPrefix.length - 1, valuesSource, Exclusion.None, sparse ? this.tweak : undefined, reference => this.buildGTIN14(indicatorDigit, reference));
1382
+ }
1383
+
1384
+ /**
1385
+ * Zero suppress a GTIN-12.
1386
+ *
1387
+ * @param gtin12
1388
+ * GTIN-12.
1389
+ *
1390
+ * @returns
1391
+ * Zero-suppressed GTIN-12.
1392
+ *
1393
+ * @throws RangeError
1394
+ */
1395
+ static zeroSuppress(gtin12: string): string {
1396
+ GTIN12_VALIDATOR.validate(gtin12);
1397
+
1398
+ // Convert to individual digits.
1399
+ const d = Array.from(gtin12);
1400
+
1401
+ let zeroSuppressedGTIN12: string | undefined;
1402
+
1403
+ // All rules require that digits in positions 1, 5, and 6 be zero.
1404
+ if (d[0] === "0" && d[6] === "0" && d[7] === "0") {
1405
+ if (d[10] >= "5" && d[8] === "0" && d[9] === "0" && d[5] !== "0") {
1406
+ zeroSuppressedGTIN12 = `0${d[1]}${d[2]}${d[3]}${d[4]}${d[5]}${d[10]}${d[11]}`;
1407
+ } else if (d[5] === "0" && d[8] === "0" && d[9] === "0" && d[4] !== "0") {
1408
+ zeroSuppressedGTIN12 = `0${d[1]}${d[2]}${d[3]}${d[4]}${d[10]}4${d[11]}`;
1409
+ } else if (d[3] <= "2" && d[4] === "0" && d[5] === "0") {
1410
+ zeroSuppressedGTIN12 = `0${d[1]}${d[2]}${d[8]}${d[9]}${d[10]}${d[3]}${d[11]}`;
1411
+ } else if (d[3] >= "3" && d[4] === "0" && d[5] === "0" && d[8] === "0") {
1412
+ zeroSuppressedGTIN12 = `0${d[1]}${d[2]}${d[3]}${d[9]}${d[10]}3${d[11]}`;
1413
+ }
1414
+ }
1415
+
1416
+ if (zeroSuppressedGTIN12 === undefined) {
1417
+ throw new RangeError(i18next.t("IdentificationKey.invalidZeroSuppressibleGTIN12", {
1418
+ ns: gs1NS
1419
+ }));
1420
+ }
1421
+
1422
+ return zeroSuppressedGTIN12;
1423
+ }
1424
+
1425
+ /**
1426
+ * Convert a GTIN of any length to a GTIN-14 with an optional indicator digit.
1427
+ *
1428
+ * @param indicatorDigit
1429
+ * Indicator digit. If blank, assumes "0" if the GTIN is not already a GTIN-14.
1430
+ *
1431
+ * @param gtin
1432
+ * GTIN.
1433
+ *
1434
+ * @returns
1435
+ * GTIN-14.
1436
+ */
1437
+ static convertToGTIN14(indicatorDigit: string, gtin: string): string {
1438
+ GTINCreator.validateAny(gtin);
1439
+
1440
+ NUMERIC_CREATOR.validate(indicatorDigit, GTINCreator.OPTIONAL_INDICATOR_DIGIT_VALIDATION);
1441
+
1442
+ const gtinLength = gtin.length;
1443
+
1444
+ // Check digit doesn't change by prepending zeros.
1445
+ let gtin14 = "0".repeat(GTINType.GTIN14 - gtinLength) + gtin;
1446
+
1447
+ // If indicator digit provided and is different, recalculate the check digit.
1448
+ if (indicatorDigit.length !== 0 && indicatorDigit !== gtin14.charAt(0)) {
1449
+ const partialGTIN14 = indicatorDigit + gtin14.substring(1, GTINType.GTIN14 - 1);
1450
+
1451
+ gtin14 = partialGTIN14 + checkDigit(partialGTIN14);
1452
+ }
1453
+
1454
+ return gtin14;
1455
+ }
1456
+
1457
+ /**
1458
+ * Normalize a GTIN of any length.
1459
+ * - A GTIN-14 that starts with six zeros or a GTIN-13 that starts with five zeros is normalized to GTIN-8.
1460
+ * - A GTIN-14 that starts with two zeros or a GTIN-13 that starts with one zero is normalized to GTIN-12.
1461
+ * - A GTIN-14 that starts with one zero is normalized to GTIN-13.
1462
+ * - Otherwise, the GTIN is unchanged.
1463
+ *
1464
+ * @param gtin
1465
+ * GTIN.
1466
+ *
1467
+ * @returns
1468
+ * Normalized GTIN.
1469
+ *
1470
+ * @throws RangeError
1471
+ */
1472
+ static normalize(gtin: string): string {
1473
+ const gtinLength = gtin.length;
1474
+
1475
+ let normalizedGTIN: string;
1476
+
1477
+ switch (gtinLength) {
1478
+ case GTINType.GTIN13 as number:
1479
+ if (!gtin.startsWith("0")) {
1480
+ // GTIN is GTIN-13.
1481
+ normalizedGTIN = gtin;
1482
+ } else if (!gtin.startsWith("00000")) {
1483
+ // GTIN is GTIN-12.
1484
+ normalizedGTIN = gtin.substring(1);
1485
+ } else if (!gtin.startsWith("000000")) {
1486
+ // GTIN is GTIN-8.
1487
+ normalizedGTIN = gtin.substring(5);
1488
+ } else {
1489
+ throw new RangeError(i18next.t("IdentificationKey.invalidZeroSuppressedGTIN12AsGTIN13", {
1490
+ ns: gs1NS
1491
+ }));
1492
+ }
1493
+ break;
1494
+
1495
+ case GTINType.GTIN12 as number:
1496
+ // GTIN is GTIN-12.
1497
+ normalizedGTIN = gtin;
1498
+ break;
1499
+
1500
+ case GTINType.GTIN8 as number:
1501
+ if (gtin.charAt(0) !== "0") {
1502
+ // GTIN is GTIN-8.
1503
+ normalizedGTIN = gtin;
1504
+ } else {
1505
+ // GTIN is zero-suppressed GTIN-12.
1506
+ normalizedGTIN = GTINCreator.zeroExpand(gtin);
1507
+ }
1508
+ break;
1509
+
1510
+ case GTINType.GTIN14 as number:
1511
+ if (!gtin.startsWith("0")) {
1512
+ // GTIN is GTIN-14.
1513
+ normalizedGTIN = gtin;
1514
+ } else if (!gtin.startsWith("00")) {
1515
+ // GTIN is GTIN-13.
1516
+ normalizedGTIN = gtin.substring(1);
1517
+ } else if (!gtin.startsWith("000000")) {
1518
+ // GTIN is GTIN-12.
1519
+ normalizedGTIN = gtin.substring(2);
1520
+ } else if (!gtin.startsWith("0000000")) {
1521
+ // GTIN is GTIN-8.
1522
+ normalizedGTIN = gtin.substring(6);
1523
+ } else {
1524
+ throw new RangeError(i18next.t("IdentificationKey.invalidZeroSuppressedGTIN12AsGTIN14", {
1525
+ ns: gs1NS
1526
+ }));
1527
+ }
1528
+ break;
1529
+
1530
+ default:
1531
+ throw new RangeError(i18next.t("IdentificationKey.invalidGTINLength", {
1532
+ ns: gs1NS
1533
+ }));
1534
+ }
1535
+
1536
+ // Validation applies to the normalized GTIN.
1537
+ GTINCreator.validateAny(normalizedGTIN);
1538
+
1539
+ return normalizedGTIN;
1540
+ }
1541
+ }
1542
+
1543
+ /**
1544
+ * Non-GTIN numeric identification key creator.
1545
+ */
1546
+ export class NonGTINNumericIdentificationKeyCreator extends Mixin(NonGTINNumericIdentificationKeyValidator, AbstractNumericIdentificationKeyCreator) {
1547
+ /**
1548
+ * Constructor. Called internally by {@link PrefixManager} non-GTIN numeric identification key creator getters;
1549
+ * should not be called by other code.
1550
+ *
1551
+ * @param prefixManager
1552
+ * Prefix manager.
1553
+ *
1554
+ * @param identificationKeyType
1555
+ * Identification key type.
1556
+ *
1557
+ * @param length
1558
+ * Length.
1559
+ *
1560
+ * @param leaderType
1561
+ * Leader type.
1562
+ */
1563
+ constructor(prefixManager: PrefixManager, identificationKeyType: IdentificationKeyType, length: number, leaderType: LeaderType = LeaderType.None) {
1564
+ super(identificationKeyType, length, leaderType);
1565
+
1566
+ this.init(prefixManager, prefixManager.gs1CompanyPrefix);
1567
+ }
1568
+ }
1569
+
1570
+ /**
1571
+ * Serializable numeric identification key creator.
1572
+ */
1573
+ export class SerializableNumericIdentificationKeyCreator extends Mixin(SerializableNumericIdentificationKeyValidator, AbstractNumericIdentificationKeyCreator) {
1574
+ /**
1575
+ * Constructor. Called internally by {@link PrefixManager} serialized numeric identification key creator getters;
1576
+ * should not be called by other code.
1577
+ *
1578
+ * @param prefixManager
1579
+ * Prefix manager.
1580
+ *
1581
+ * @param identificationKeyType
1582
+ * Identification key type.
1583
+ *
1584
+ * @param length
1585
+ * Length.
1586
+ *
1587
+ * @param serialComponentLength
1588
+ * Serial component length.
1589
+ *
1590
+ * @param serialComponentCharacterSet
1591
+ * Serial component character set.
1592
+ */
1593
+ constructor(prefixManager: PrefixManager, identificationKeyType: IdentificationKeyType, length: number, serialComponentLength: number, serialComponentCharacterSet: CharacterSet) {
1594
+ super(identificationKeyType, length, serialComponentLength, serialComponentCharacterSet);
1595
+
1596
+ this.init(prefixManager, prefixManager.gs1CompanyPrefix);
1597
+ }
1598
+
1599
+ /**
1600
+ * Get the serial component creator.
1601
+ */
1602
+ get serialComponentCreator(): CharacterSetCreator {
1603
+ return this.serialComponentValidator as CharacterSetCreator;
1604
+ }
1605
+
1606
+ /**
1607
+ * Concatenate a validated base identification key with a serial component.
1608
+ *
1609
+ * @param baseIdentificationKey
1610
+ * Base identification key.
1611
+ *
1612
+ * @param serialComponent
1613
+ * Serial component.
1614
+ *
1615
+ * @returns
1616
+ * Serialized identification key.
1617
+ */
1618
+ private concatenateValidated(baseIdentificationKey: string, serialComponent: string): string {
1619
+ this.serialComponentCreator.validate(serialComponent, this.serialComponentValidation);
1620
+
1621
+ return baseIdentificationKey + serialComponent;
1622
+ }
1623
+
1624
+ /**
1625
+ * Concatenate a validated base identification key with multiple serial components.
1626
+ *
1627
+ * @param baseIdentificationKey
1628
+ * Base identification key.
1629
+ *
1630
+ * @param serialComponentsSource
1631
+ * Source of serial components.
1632
+ *
1633
+ * @returns
1634
+ * Serialized identification keys.
1635
+ */
1636
+ private concatenateValidatedMultiple(baseIdentificationKey: string, serialComponentsSource: IterationSource<string>): IterableIterator<string> {
1637
+ return IterationHelper.from(serialComponentsSource).map(serialComponent => this.concatenateValidated(baseIdentificationKey, serialComponent));
1638
+ }
1639
+
1640
+ /**
1641
+ * Create a serialized identification key with a reference based on a numeric value and a serial component. The
1642
+ * value is converted to a reference of the appropriate length using {@linkcode NUMERIC_CREATOR}.
1643
+ *
1644
+ * @param value
1645
+ * Numeric value.
1646
+ *
1647
+ * @param serialComponent
1648
+ * Serial component.
1649
+ *
1650
+ * @param sparse
1651
+ * If true, the value is mapped to a sparse sequence resistant to discovery. Default is false.
1652
+ *
1653
+ * @returns
1654
+ * Serialized identification key.
1655
+ */
1656
+ createSerialized(value: number, serialComponent: string, sparse = false): string {
1657
+ return this.concatenateValidated(this.create(value, sparse), serialComponent);
1658
+ }
1659
+
1660
+ /**
1661
+ * Create multiple serialized identification keys with a reference based on a numeric value and multiple serial
1662
+ * components. The value is converted to a reference of the appropriate length using {@linkcode NUMERIC_CREATOR}.
1663
+ *
1664
+ * @param value
1665
+ * Numeric value.
1666
+ *
1667
+ * @param serialComponentsSource
1668
+ * Source of serial components.
1669
+ *
1670
+ * @param sparse
1671
+ * If true, the value is mapped to a sparse sequence resistant to discovery. Default is false.
1672
+ *
1673
+ * @returns
1674
+ * Serialized identification keys.
1675
+ */
1676
+ createMultipleSerialized(value: number, serialComponentsSource: IterationSource<string>, sparse = false): IterableIterator<string> {
1677
+ return this.concatenateValidatedMultiple(this.create(value, sparse), serialComponentsSource);
1678
+ }
1679
+
1680
+ /**
1681
+ * Concatenate a base identification key with a serial component.
1682
+ *
1683
+ * @param baseIdentificationKey
1684
+ * Base identification key.
1685
+ *
1686
+ * @param serialComponent
1687
+ * Serial component.
1688
+ *
1689
+ * @returns
1690
+ * Serialized identification key.
1691
+ */
1692
+ concatenate(baseIdentificationKey: string, serialComponent: string): string {
1693
+ this.validate(baseIdentificationKey);
1694
+
1695
+ return this.concatenateValidated(baseIdentificationKey, serialComponent);
1696
+ }
1697
+
1698
+ /**
1699
+ * Concatenate a base identification key with multiple serial components.
1700
+ *
1701
+ * @param baseIdentificationKey
1702
+ * Base identification key.
1703
+ *
1704
+ * @param serialComponentsSource
1705
+ * Source of serial components.
1706
+ *
1707
+ * @returns
1708
+ * Serialized identification keys.
1709
+ */
1710
+ concatenateMultiple(baseIdentificationKey: string, serialComponentsSource: IterationSource<string>): IterableIterator<string> {
1711
+ this.validate(baseIdentificationKey);
1712
+
1713
+ return this.concatenateValidatedMultiple(baseIdentificationKey, serialComponentsSource);
1714
+ }
1715
+ }
1716
+
1717
+ /**
1718
+ * Non-numeric identification key creator.
1719
+ */
1720
+ export class NonNumericIdentificationKeyCreator extends Mixin(NonNumericIdentificationKeyValidator, AbstractIdentificationKeyCreator) {
1721
+ /**
1722
+ * Reference character set validation parameters.
1723
+ */
1724
+ private readonly _referenceValidation: CharacterSetValidation;
1725
+
1726
+ /**
1727
+ * Constructor. Called internally by {@link PrefixManager} non-numeric identification key creator getters; should
1728
+ * not be called by other code.
1729
+ *
1730
+ * @param prefixManager
1731
+ * Prefix manager.
1732
+ *
1733
+ * @param identificationKeyType
1734
+ * Identification key type.
1735
+ *
1736
+ * @param length
1737
+ * Length.
1738
+ *
1739
+ * @param referenceCharacterSet
1740
+ * Reference character set.
1741
+ *
1742
+ * @param requiresCheckCharacterPair
1743
+ * True if the identification key requires a check character pair.
1744
+ */
1745
+ constructor(prefixManager: PrefixManager, identificationKeyType: IdentificationKeyType, length: number, referenceCharacterSet: CharacterSet, requiresCheckCharacterPair = false) {
1746
+ super(identificationKeyType, length, referenceCharacterSet, requiresCheckCharacterPair);
1747
+
1748
+ this.init(prefixManager, prefixManager.gs1CompanyPrefix, 2 * Number(requiresCheckCharacterPair));
1749
+
1750
+ this._referenceValidation = {
1751
+ minimumLength: 1,
1752
+ // Maximum reference length has to account for prefix and check character pair.
1753
+ maximumLength: this.referenceLength,
1754
+ component: () => i18next.t("IdentificationKey.reference", {
1755
+ ns: gs1NS
1756
+ })
1757
+ };
1758
+ }
1759
+
1760
+ /**
1761
+ * Create an identification key with a reference.
1762
+ *
1763
+ * @param reference
1764
+ * Reference.
1765
+ *
1766
+ * @returns
1767
+ * Identification key.
1768
+ */
1769
+ create(reference: string): string {
1770
+ this.referenceValidator.validate(reference, this._referenceValidation);
1771
+
1772
+ const partialIdentificationKey = this.prefix + reference;
1773
+
1774
+ return this.requiresCheckCharacterPair ? partialIdentificationKey + checkCharacterPair(partialIdentificationKey) : partialIdentificationKey;
1775
+ }
1776
+
1777
+ /**
1778
+ * Create multiple identification keys with references.
1779
+ *
1780
+ * @param referencesSource
1781
+ * Source of references.
1782
+ *
1783
+ * @returns
1784
+ * Identification keys.
1785
+ */
1786
+ createMultiple(referencesSource: IterationSource<string>): IterableIterator<string> {
1787
+ return IterationHelper.from(referencesSource).map(reference => this.create(reference));
1788
+ }
1789
+ }
1790
+
1791
+ /**
1792
+ * Prefix manager. This is the core class for identification key creation.
1793
+ *
1794
+ * A prefix manager may be created for any {@link PrefixType | prefix type}. As most applications work with a limited
1795
+ * number of prefixes for creating identification keys, prefix managers are cached in memory and may be reused.
1796
+ *
1797
+ * Prefix managers are keyed by GS1 Company Prefix, so the prefix type that is requested may not match the prefix type
1798
+ * of the returned prefix manager. For example, the prefix manager for GS1 Company Prefix 0614141 is identical to the
1799
+ * one for U.P.C. Company Prefix 614141, with the prefix type equal to {@link PrefixType.UPCCompanyPrefix} and the
1800
+ * prefix equal to "614141".
1801
+ *
1802
+ * To support the creation of sparse identification keys, a prefix manager maintains a {@link tweakFactor | tweak
1803
+ * factor} which is used, along with a type-specific multiplier, as the tweak when creating numeric identification keys.
1804
+ * The default tweak factor is the numeric value of the GS1 Company Prefix representation of the prefix preceded by '1'
1805
+ * to ensure uniqueness (i.e., so that prefixes 0 N1 N2 N3... and N1 N2 N3... produce different tweak factors). This is
1806
+ * usually sufficient for obfuscation, but as the sparse creation algorithm is reversible and as the GS1 Company Prefix
1807
+ * is discoverable via {@link https://www.gs1.org/services/verified-by-gs1 | Verified by GS1}, a user-defined tweak
1808
+ * factor should be used if a higher degree of obfuscation is required. When using a tweak factor other than the
1809
+ * default, care should be taken to restore it when resuming the application. A tweak factor of 0 creates a straight
1810
+ * sequence.
1811
+ */
1812
+ export class PrefixManager {
1813
+ /**
1814
+ * Cached prefix managers, keyed by GS1 Company Prefix.
1815
+ */
1816
+ private static readonly PREFIX_MANAGERS_MAP = new Map<string, PrefixManager>();
1817
+
1818
+ /**
1819
+ * GS1 Company Prefix minimum length.
1820
+ */
1821
+ static readonly GS1_COMPANY_PREFIX_MINIMUM_LENGTH = 4;
1822
+
1823
+ /**
1824
+ * GS1 Company Prefix maximum length.
1825
+ */
1826
+ static readonly GS1_COMPANY_PREFIX_MAXIMUM_LENGTH = 12;
1827
+
1828
+ /**
1829
+ * U.P.C. Company Prefix minimum length.
1830
+ */
1831
+ static readonly UPC_COMPANY_PREFIX_MINIMUM_LENGTH = 6;
1832
+
1833
+ /**
1834
+ * U.P.C. Company Prefix maximum length.
1835
+ */
1836
+ static readonly UPC_COMPANY_PREFIX_MAXIMUM_LENGTH = 11;
1837
+
1838
+ /**
1839
+ * GS1-8 Prefix minimum length.
1840
+ */
1841
+ static readonly GS1_8_PREFIX_MINIMUM_LENGTH = 2;
1842
+
1843
+ /**
1844
+ * GS1-8 Prefix maximum length.
1845
+ */
1846
+ static readonly GS1_8_PREFIX_MAXIMUM_LENGTH = 7;
1847
+
1848
+ /**
1849
+ * Validation parameters for GS1 Company Prefix.
1850
+ */
1851
+ private static readonly GS1_COMPANY_PREFIX_VALIDATION: CharacterSetValidation = {
1852
+ minimumLength: PrefixManager.GS1_COMPANY_PREFIX_MINIMUM_LENGTH,
1853
+ maximumLength: PrefixManager.GS1_COMPANY_PREFIX_MAXIMUM_LENGTH,
1854
+ component: () => i18next.t("Prefix.gs1CompanyPrefix", {
1855
+ ns: gs1NS
1856
+ })
1857
+ };
1858
+
1859
+ /**
1860
+ * Validation parameters for U.P.C. Company Prefix expressed as GS1 Company Prefix.
1861
+ */
1862
+ private static readonly UPC_COMPANY_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION: CharacterSetValidation = {
1863
+ minimumLength: PrefixManager.UPC_COMPANY_PREFIX_MINIMUM_LENGTH + 1,
1864
+ maximumLength: PrefixManager.UPC_COMPANY_PREFIX_MAXIMUM_LENGTH + 1,
1865
+ component: () => i18next.t("Prefix.gs1CompanyPrefix", {
1866
+ ns: gs1NS
1867
+ })
1868
+ };
1869
+
1870
+ /**
1871
+ * Validation parameters for GS1-8 Prefix expressed as GS1 Company Prefix.
1872
+ */
1873
+ private static readonly GS1_8_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION: CharacterSetValidation = {
1874
+ minimumLength: PrefixManager.GS1_8_PREFIX_MINIMUM_LENGTH + 5,
1875
+ maximumLength: PrefixManager.GS1_8_PREFIX_MAXIMUM_LENGTH + 5,
1876
+ component: () => i18next.t("Prefix.gs1CompanyPrefix", {
1877
+ ns: gs1NS
1878
+ })
1879
+ };
1880
+
1881
+ /**
1882
+ * Validation parameters for U.P.C. Company Prefix.
1883
+ */
1884
+ private static readonly UPC_COMPANY_PREFIX_VALIDATION: CharacterSetValidation = {
1885
+ minimumLength: PrefixManager.UPC_COMPANY_PREFIX_MINIMUM_LENGTH,
1886
+ maximumLength: PrefixManager.UPC_COMPANY_PREFIX_MAXIMUM_LENGTH,
1887
+ component: () => i18next.t("Prefix.upcCompanyPrefix", {
1888
+ ns: gs1NS
1889
+ })
1890
+ };
1891
+
1892
+ /**
1893
+ * Validation parameters for GS1-8 Prefix.
1894
+ */
1895
+ private static readonly GS1_8_PREFIX_VALIDATION: CharacterSetValidation = {
1896
+ minimumLength: PrefixManager.GS1_8_PREFIX_MINIMUM_LENGTH,
1897
+ maximumLength: PrefixManager.GS1_8_PREFIX_MAXIMUM_LENGTH,
1898
+ component: () => i18next.t("Prefix.gs18Prefix", {
1899
+ ns: gs1NS
1900
+ })
1901
+ };
1902
+
1903
+ /**
1904
+ * Creator tweak factors. Different numeric identification key types have different tweak factors so that sparse
1905
+ * creation generates different sequences for each.
1906
+ */
1907
+ private static readonly CREATOR_TWEAK_FACTORS_MAP = new Map<IdentificationKeyType, bigint>([
1908
+ [IdentificationKeyType.GTIN, 1987n],
1909
+ [IdentificationKeyType.GLN, 4241n],
1910
+ [IdentificationKeyType.SSCC, 8087n],
1911
+ [IdentificationKeyType.GRAI, 3221n],
1912
+ [IdentificationKeyType.GSRN, 2341n],
1913
+ [IdentificationKeyType.GDTI, 7333n],
1914
+ [IdentificationKeyType.GSIN, 5623n],
1915
+ [IdentificationKeyType.GCN, 6869n]
1916
+ ]);
1917
+
1918
+ /**
1919
+ * Normalized prefix type.
1920
+ */
1921
+ private readonly _prefixType: PrefixType;
1922
+
1923
+ /**
1924
+ * Normalized prefix.
1925
+ */
1926
+ private readonly _prefix: string;
1927
+
1928
+ /**
1929
+ * Prefix as GS1 Company Prefix.
1930
+ */
1931
+ private readonly _gs1CompanyPrefix: string;
1932
+
1933
+ /**
1934
+ * U.P.C. Company Prefix if prefix type is {@link PrefixType.UPCCompanyPrefix}.
1935
+ */
1936
+ private readonly _upcCompanyPrefix: string | undefined;
1937
+
1938
+ /**
1939
+ * GS1-8 Prefix if prefix type is {@link PrefixType.GS18Prefix}.
1940
+ */
1941
+ private readonly _gs18Prefix: string | undefined;
1942
+
1943
+ /**
1944
+ * Tweak factor.
1945
+ */
1946
+ private _tweakFactor = 0n;
1947
+
1948
+ /**
1949
+ * Cached identification key creators.
1950
+ */
1951
+ private readonly _identificationKeyCreatorsMap = new Map<IdentificationKeyType, IdentificationKeyCreator>();
1952
+
1953
+ /**
1954
+ * Constructor.
1955
+ *
1956
+ * @param gs1CompanyPrefix
1957
+ * GS1 Company Prefix.
1958
+ */
1959
+ private constructor(gs1CompanyPrefix: string) {
1960
+ this._gs1CompanyPrefix = gs1CompanyPrefix;
1961
+
1962
+ // Determine the prefix type and populate the remaining fields.
1963
+ if (!gs1CompanyPrefix.startsWith("0")) {
1964
+ this._prefixType = PrefixType.GS1CompanyPrefix;
1965
+ this._prefix = this._gs1CompanyPrefix;
1966
+ } else if (!gs1CompanyPrefix.startsWith("00000")) {
1967
+ this._prefixType = PrefixType.UPCCompanyPrefix;
1968
+ this._upcCompanyPrefix = gs1CompanyPrefix.substring(1);
1969
+ this._prefix = this._upcCompanyPrefix;
1970
+ } else {
1971
+ this._prefixType = PrefixType.GS18Prefix;
1972
+ this._gs18Prefix = gs1CompanyPrefix.substring(5);
1973
+ this._prefix = this._gs18Prefix;
1974
+ }
1975
+
1976
+ this.resetTweakFactor();
1977
+ }
1978
+
1979
+ /**
1980
+ * Get the prefix type.
1981
+ */
1982
+ get prefixType(): PrefixType {
1983
+ return this._prefixType;
1984
+ }
1985
+
1986
+ /**
1987
+ * Get the prefix.
1988
+ */
1989
+ get prefix(): string {
1990
+ return this._prefix;
1991
+ }
1992
+
1993
+ /**
1994
+ * Get the GS1 Company Prefix.
1995
+ */
1996
+ get gs1CompanyPrefix(): string {
1997
+ return this._gs1CompanyPrefix;
1998
+ }
1999
+
2000
+ /**
2001
+ * Get the U.P.C. Company Prefix if prefix type is {@link PrefixType.UPCCompanyPrefix} or undefined if not.
2002
+ */
2003
+ get upcCompanyPrefix(): string | undefined {
2004
+ return this._upcCompanyPrefix;
2005
+ }
2006
+
2007
+ /**
2008
+ * Get the GS1-8 Prefix if prefix type is {@link PrefixType.GS18Prefix} or undefined if not.
2009
+ */
2010
+ get gs18Prefix(): string | undefined {
2011
+ return this._gs18Prefix;
2012
+ }
2013
+
2014
+ /**
2015
+ * Set the tweak for an identification key creator if it's a numeric identification key creator.
2016
+ *
2017
+ * @param creator
2018
+ * Identification key creator.
2019
+ */
2020
+ private setCreatorTweak(creator: IdentificationKeyCreator): void {
2021
+ const creatorTweakFactor = PrefixManager.CREATOR_TWEAK_FACTORS_MAP.get(creator.identificationKeyType);
2022
+
2023
+ // Creator tweak factor is defined for numeric identification keys only.
2024
+ if (creatorTweakFactor !== undefined) {
2025
+ // Explicit cast without testing is necessary as "instanceof" doesn't work for mixin types.
2026
+ (creator as AbstractNumericIdentificationKeyCreator).tweak = this.tweakFactor * creatorTweakFactor;
2027
+ }
2028
+ }
2029
+
2030
+ /**
2031
+ * Get the tweak factor.
2032
+ */
2033
+ get tweakFactor(): bigint {
2034
+ return this._tweakFactor;
2035
+ }
2036
+
2037
+ /**
2038
+ * Set the tweak factor.
2039
+ *
2040
+ * @param value
2041
+ * Tweak factor.
2042
+ */
2043
+ set tweakFactor(value: number | bigint) {
2044
+ const tweakFactor = BigInt(value);
2045
+
2046
+ if (this._tweakFactor !== tweakFactor) {
2047
+ this._tweakFactor = tweakFactor;
2048
+
2049
+ this._identificationKeyCreatorsMap.forEach((creator) => {
2050
+ this.setCreatorTweak(creator);
2051
+ });
2052
+ }
2053
+ }
2054
+
2055
+ /**
2056
+ * Reset the tweak factor to its default (numeric value of the GS1 Company Prefix preceded by '1').
2057
+ */
2058
+ resetTweakFactor(): void {
2059
+ // Default tweak factor is the numeric value of the GS1 Company Prefix preceded by '1'.
2060
+ this.tweakFactor = BigInt("1" + this.gs1CompanyPrefix);
2061
+ }
2062
+
2063
+ /**
2064
+ * Get a prefix manager.
2065
+ *
2066
+ * @param prefixType
2067
+ * Prefix type.
2068
+ *
2069
+ * @param prefix
2070
+ * Prefix.
2071
+ *
2072
+ * @returns
2073
+ * Prefix manager with normalized prefix type and prefix.
2074
+ */
2075
+ static get(prefixType: PrefixType, prefix: string): PrefixManager {
2076
+ PrefixManager.validatePrefix(prefixType, true, true, prefix);
2077
+
2078
+ let gs1CompanyPrefix: string;
2079
+
2080
+ switch (prefixType) {
2081
+ case PrefixType.GS1CompanyPrefix:
2082
+ gs1CompanyPrefix = prefix;
2083
+ break;
2084
+
2085
+ case PrefixType.UPCCompanyPrefix:
2086
+ gs1CompanyPrefix = "0" + prefix;
2087
+ break;
2088
+
2089
+ case PrefixType.GS18Prefix:
2090
+ gs1CompanyPrefix = "00000" + prefix;
2091
+ break;
2092
+ }
2093
+
2094
+ let prefixManager = PrefixManager.PREFIX_MANAGERS_MAP.get(gs1CompanyPrefix);
2095
+
2096
+ if (prefixManager === undefined) {
2097
+ prefixManager = new PrefixManager(gs1CompanyPrefix);
2098
+ PrefixManager.PREFIX_MANAGERS_MAP.set(gs1CompanyPrefix, prefixManager);
2099
+ }
2100
+
2101
+ return prefixManager;
2102
+ }
2103
+
2104
+ /**
2105
+ * Validate a prefix.
2106
+ *
2107
+ * @param prefixType
2108
+ * Prefix type.
2109
+ *
2110
+ * @param allowUPCCompanyPrefix
2111
+ * If true, a U.P.C. Company Prefix expressed as a GS1 Company Prefix is permitted.
2112
+ *
2113
+ * @param allowGS18Prefix
2114
+ * If true, a GS1-8 Prefix expressed as a GS1 Company Prefix is permitted.
2115
+ *
2116
+ * @param prefix
2117
+ * Prefix.
2118
+ *
2119
+ * @param isFromIdentificationKey
2120
+ * If true, the prefix is from an identification key and should be trimmed before its character set is validated.
2121
+ *
2122
+ * @param isNumericIdentificationKey
2123
+ * If true, the prefix is from a numeric identification key and its character set will be validated by the caller.
2124
+ *
2125
+ * @param positionOffset
2126
+ * Position offset within a larger string.
2127
+ *
2128
+ * @throws RangeError
2129
+ */
2130
+ static validatePrefix(prefixType: PrefixType, allowUPCCompanyPrefix: boolean, allowGS18Prefix: boolean, prefix: string, isFromIdentificationKey = false, isNumericIdentificationKey = false, positionOffset?: number): void {
2131
+ let baseValidation: CharacterSetValidation;
2132
+
2133
+ // Validate the prefix type and determine the prefix validation parameters.
2134
+ switch (prefixType) {
2135
+ case PrefixType.GS1CompanyPrefix:
2136
+ if (!prefix.startsWith("0")) {
2137
+ baseValidation = PrefixManager.GS1_COMPANY_PREFIX_VALIDATION;
2138
+ } else if (!prefix.startsWith("00000")) {
2139
+ if (!allowUPCCompanyPrefix) {
2140
+ throw new RangeError(i18next.t("Prefix.gs1CompanyPrefixCantStartWith0", {
2141
+ ns: gs1NS
2142
+ }));
2143
+ }
2144
+
2145
+ baseValidation = PrefixManager.UPC_COMPANY_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION;
2146
+ } else if (!prefix.startsWith("000000")) {
2147
+ if (!allowGS18Prefix) {
2148
+ throw new RangeError(i18next.t("Prefix.gs1CompanyPrefixCantStartWith00000", {
2149
+ ns: gs1NS
2150
+ }));
2151
+ }
2152
+
2153
+ baseValidation = PrefixManager.GS1_8_PREFIX_AS_GS1_COMPANY_PREFIX_VALIDATION;
2154
+ } else {
2155
+ throw new RangeError(i18next.t("Prefix.gs1CompanyPrefixCantStartWith000000", {
2156
+ ns: gs1NS
2157
+ }));
2158
+ }
2159
+ break;
2160
+
2161
+ case PrefixType.UPCCompanyPrefix:
2162
+ if (prefix.startsWith("0000")) {
2163
+ throw new RangeError(i18next.t("Prefix.upcCompanyPrefixCantStartWith0000", {
2164
+ ns: gs1NS
2165
+ }));
2166
+ }
2167
+
2168
+ baseValidation = PrefixManager.UPC_COMPANY_PREFIX_VALIDATION;
2169
+ break;
2170
+
2171
+ case PrefixType.GS18Prefix:
2172
+ if (prefix.startsWith("0")) {
2173
+ throw new RangeError(i18next.t("Prefix.gs18PrefixCantStartWith0", {
2174
+ ns: gs1NS
2175
+ }));
2176
+ }
2177
+
2178
+ baseValidation = PrefixManager.GS1_8_PREFIX_VALIDATION;
2179
+ break;
2180
+ }
2181
+
2182
+ const mergedValidation: CharacterSetValidation = {
2183
+ ...baseValidation,
2184
+ positionOffset
2185
+ };
2186
+
2187
+ // If from key and numeric, key validation will take care of character set validation.
2188
+ if (!isFromIdentificationKey) {
2189
+ NUMERIC_CREATOR.validate(prefix, mergedValidation);
2190
+ } else if (!isNumericIdentificationKey) {
2191
+ // Validate only the minimum length, allowing at least one character for the (possibly non-numeric) reference.
2192
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2193
+ NUMERIC_CREATOR.validate(prefix.substring(0, Math.min(mergedValidation.minimumLength!, prefix.length - 1)), mergedValidation);
2194
+ }
2195
+ }
2196
+
2197
+ /**
2198
+ * Get an identification key creator.
2199
+ *
2200
+ * @param identificationKeyType
2201
+ * Identification key type.
2202
+ *
2203
+ * @param constructorCallback
2204
+ * Constructor callback.
2205
+ *
2206
+ * @returns
2207
+ * Identification key creator.
2208
+ *
2209
+ * @throws RangeError
2210
+ */
2211
+ private getIdentificationKeyCreator<T extends IdentificationKeyCreator>(identificationKeyType: IdentificationKeyType, constructorCallback: () => T): T {
2212
+ let creator = this._identificationKeyCreatorsMap.get(identificationKeyType) as (T | undefined);
2213
+
2214
+ if (creator === undefined) {
2215
+ if (this.prefixType === PrefixType.GS18Prefix && identificationKeyType !== IdentificationKeyType.GTIN) {
2216
+ throw new RangeError(i18next.t("Prefix.identificationKeyTypeNotSupportedByGS18Prefix", {
2217
+ ns: gs1NS,
2218
+ identificationKeyType
2219
+ }));
2220
+ }
2221
+
2222
+ creator = constructorCallback();
2223
+
2224
+ this.setCreatorTweak(creator);
2225
+ }
2226
+
2227
+ return creator;
2228
+ }
2229
+
2230
+ /**
2231
+ * Get non-GTIN numeric identification key creator.
2232
+ *
2233
+ * @param validator
2234
+ * Validator on which identification key creator is based.
2235
+ *
2236
+ * @returns
2237
+ * Identification key creator.
2238
+ */
2239
+ private getNonGTINNumericIdentificationKeyCreator(validator: NonGTINNumericIdentificationKeyValidator): NonGTINNumericIdentificationKeyCreator {
2240
+ return this.getIdentificationKeyCreator(validator.identificationKeyType, () => new NonGTINNumericIdentificationKeyCreator(this, validator.identificationKeyType, validator.length, validator.leaderType));
2241
+ }
2242
+
2243
+ /**
2244
+ * Get serialized numeric identification key creator.
2245
+ *
2246
+ * @param validator
2247
+ * Validator on which identification key creator is based.
2248
+ *
2249
+ * @returns
2250
+ * Identification key creator.
2251
+ */
2252
+ private getSerializableNumericIdentificationKeyCreator(validator: SerializableNumericIdentificationKeyValidator): SerializableNumericIdentificationKeyCreator {
2253
+ return this.getIdentificationKeyCreator(validator.identificationKeyType, () => new SerializableNumericIdentificationKeyCreator(this, validator.identificationKeyType, validator.length, validator.serialComponentLength, validator.serialComponentCharacterSet));
2254
+ }
2255
+
2256
+ /**
2257
+ * Get non-numeric identification key creator.
2258
+ *
2259
+ * @param validator
2260
+ * Validator on which identification key creator is based.
2261
+ *
2262
+ * @returns
2263
+ * Identification key creator.
2264
+ */
2265
+ private getNonNumericIdentificationKeyCreator(validator: NonNumericIdentificationKeyValidator): NonNumericIdentificationKeyCreator {
2266
+ return this.getIdentificationKeyCreator(validator.identificationKeyType, () => new NonNumericIdentificationKeyCreator(this, validator.identificationKeyType, validator.length, validator.referenceCharacterSet, validator.requiresCheckCharacterPair));
2267
+ }
2268
+
2269
+ /**
2270
+ * Get GTIN creator.
2271
+ */
2272
+ get gtinCreator(): GTINCreator {
2273
+ return this.getIdentificationKeyCreator(IdentificationKeyType.GTIN, () => {
2274
+ let gtinType: GTINType;
2275
+
2276
+ switch (this.prefixType) {
2277
+ case PrefixType.GS1CompanyPrefix:
2278
+ gtinType = GTINType.GTIN13;
2279
+ break;
2280
+
2281
+ case PrefixType.UPCCompanyPrefix:
2282
+ gtinType = GTINType.GTIN12;
2283
+ break;
2284
+
2285
+ case PrefixType.GS18Prefix:
2286
+ gtinType = GTINType.GTIN8;
2287
+ break;
2288
+ }
2289
+
2290
+ return new GTINCreator(this, gtinType);
2291
+ });
2292
+ }
2293
+
2294
+ /**
2295
+ * Get GLN creator.
2296
+ */
2297
+ get glnCreator(): NonGTINNumericIdentificationKeyCreator {
2298
+ return this.getNonGTINNumericIdentificationKeyCreator(GLN_VALIDATOR);
2299
+ }
2300
+
2301
+ /**
2302
+ * Get SSCC creator.
2303
+ */
2304
+ get ssccCreator(): NonGTINNumericIdentificationKeyCreator {
2305
+ return this.getNonGTINNumericIdentificationKeyCreator(SSCC_VALIDATOR);
2306
+ }
2307
+
2308
+ /**
2309
+ * Get GRAI creator.
2310
+ */
2311
+ get graiCreator(): SerializableNumericIdentificationKeyCreator {
2312
+ return this.getSerializableNumericIdentificationKeyCreator(GRAI_VALIDATOR);
2313
+ }
2314
+
2315
+ /**
2316
+ * Get GIAI creator.
2317
+ */
2318
+ get giaiCreator(): NonNumericIdentificationKeyCreator {
2319
+ return this.getNonNumericIdentificationKeyCreator(GIAI_VALIDATOR);
2320
+ }
2321
+
2322
+ /**
2323
+ * Get GSRN creator.
2324
+ */
2325
+ get gsrnCreator(): NonGTINNumericIdentificationKeyCreator {
2326
+ return this.getNonGTINNumericIdentificationKeyCreator(GSRN_VALIDATOR);
2327
+ }
2328
+
2329
+ /**
2330
+ * Get GDTI creator.
2331
+ */
2332
+ get gdtiCreator(): SerializableNumericIdentificationKeyCreator {
2333
+ return this.getSerializableNumericIdentificationKeyCreator(GDTI_VALIDATOR);
2334
+ }
2335
+
2336
+ /**
2337
+ * Get GINC creator.
2338
+ */
2339
+ get gincCreator(): NonNumericIdentificationKeyCreator {
2340
+ return this.getNonNumericIdentificationKeyCreator(GINC_VALIDATOR);
2341
+ }
2342
+
2343
+ /**
2344
+ * Get GSIN creator.
2345
+ */
2346
+ get gsinCreator(): NonGTINNumericIdentificationKeyCreator {
2347
+ return this.getNonGTINNumericIdentificationKeyCreator(GSIN_VALIDATOR);
2348
+ }
2349
+
2350
+ /**
2351
+ * Get GCN creator.
2352
+ */
2353
+ get gcnCreator(): SerializableNumericIdentificationKeyCreator {
2354
+ return this.getSerializableNumericIdentificationKeyCreator(GCN_VALIDATOR);
2355
+ }
2356
+
2357
+ /**
2358
+ * Get CPID creator.
2359
+ */
2360
+ get cpidCreator(): NonNumericIdentificationKeyCreator {
2361
+ return this.getNonNumericIdentificationKeyCreator(CPID_VALIDATOR);
2362
+ }
2363
+
2364
+ /**
2365
+ * Get GMN creator.
2366
+ */
2367
+ get gmnCreator(): NonNumericIdentificationKeyCreator {
2368
+ return this.getNonNumericIdentificationKeyCreator(GMN_VALIDATOR);
2369
+ }
2370
+ }