@aidc-toolkit/gs1 1.0.31-beta → 1.0.33-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.
- package/README.md +290 -17
- package/dist/character-set.d.ts +17 -2
- package/dist/character-set.d.ts.map +1 -1
- package/dist/character-set.js +44 -11
- package/dist/character-set.js.map +1 -1
- package/dist/gcp-length-cache.d.ts +81 -0
- package/dist/gcp-length-cache.d.ts.map +1 -0
- package/dist/gcp-length-cache.js +232 -0
- package/dist/gcp-length-cache.js.map +1 -0
- package/dist/gcp-length-data.d.ts +108 -0
- package/dist/gcp-length-data.d.ts.map +1 -0
- package/dist/gcp-length-data.js +53 -0
- package/dist/gcp-length-data.js.map +1 -0
- package/dist/gcp-length.d.ts +61 -0
- package/dist/gcp-length.d.ts.map +1 -0
- package/dist/gcp-length.js +300 -0
- package/dist/gcp-length.js.map +1 -0
- package/dist/gtin-creator.d.ts +0 -17
- package/dist/gtin-creator.d.ts.map +1 -1
- package/dist/gtin-creator.js +1 -93
- package/dist/gtin-creator.js.map +1 -1
- package/dist/gtin-validator.d.ts +1 -46
- package/dist/gtin-validator.d.ts.map +1 -1
- package/dist/gtin-validator.js +31 -125
- package/dist/gtin-validator.js.map +1 -1
- package/dist/identifier-validator.d.ts +1 -4
- package/dist/identifier-validator.d.ts.map +1 -1
- package/dist/identifier-validator.js +2 -5
- package/dist/identifier-validator.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/locale/en/locale-resources.d.ts +39 -35
- package/dist/locale/en/locale-resources.d.ts.map +1 -1
- package/dist/locale/en/locale-resources.js +6 -2
- package/dist/locale/en/locale-resources.js.map +1 -1
- package/dist/locale/fr/locale-resources.d.ts +39 -35
- package/dist/locale/fr/locale-resources.d.ts.map +1 -1
- package/dist/locale/fr/locale-resources.js +6 -2
- package/dist/locale/fr/locale-resources.js.map +1 -1
- package/dist/locale/i18n.d.ts +4 -4
- package/dist/locale/i18n.d.ts.map +1 -1
- package/dist/locale/i18n.js +6 -7
- package/dist/locale/i18n.js.map +1 -1
- package/dist/non-numeric-identifier-validator.js +1 -1
- package/dist/non-numeric-identifier-validator.js.map +1 -1
- package/dist/numeric-identifier-validator.d.ts.map +1 -1
- package/dist/numeric-identifier-validator.js +2 -2
- package/dist/numeric-identifier-validator.js.map +1 -1
- package/dist/prefix-manager.d.ts +35 -0
- package/dist/prefix-manager.d.ts.map +1 -1
- package/dist/prefix-manager.js +56 -0
- package/dist/prefix-manager.js.map +1 -1
- package/dist/prefix-validator.d.ts +5 -4
- package/dist/prefix-validator.d.ts.map +1 -1
- package/dist/prefix-validator.js +18 -22
- package/dist/prefix-validator.js.map +1 -1
- package/dist/serializable-numeric-identifier-validator.d.ts +26 -0
- package/dist/serializable-numeric-identifier-validator.d.ts.map +1 -1
- package/dist/serializable-numeric-identifier-validator.js +19 -0
- package/dist/serializable-numeric-identifier-validator.js.map +1 -1
- package/dist/variable-measure.d.ts +68 -0
- package/dist/variable-measure.d.ts.map +1 -0
- package/dist/variable-measure.js +210 -0
- package/dist/variable-measure.js.map +1 -0
- package/dist/verified-by-gs1.d.ts +22 -0
- package/dist/verified-by-gs1.d.ts.map +1 -0
- package/dist/verified-by-gs1.js +46 -0
- package/dist/verified-by-gs1.js.map +1 -0
- package/package.json +12 -11
- package/src/character-set.ts +55 -11
- package/src/gcp-length-cache.ts +271 -0
- package/src/gcp-length-data.ts +136 -0
- package/src/gcp-length.ts +380 -0
- package/src/gtin-creator.ts +1 -117
- package/src/gtin-validator.ts +42 -173
- package/src/identifier-validator.ts +2 -5
- package/src/index.ts +7 -1
- package/src/locale/en/locale-resources.ts +7 -3
- package/src/locale/fr/locale-resources.ts +7 -3
- package/src/locale/i18n.ts +7 -8
- package/src/locale/i18next.d.ts +2 -0
- package/src/non-numeric-identifier-validator.ts +1 -1
- package/src/numeric-identifier-validator.ts +3 -3
- package/src/prefix-manager.ts +65 -0
- package/src/prefix-validator.ts +19 -23
- package/src/serializable-numeric-identifier-validator.ts +36 -0
- package/src/variable-measure.ts +268 -0
- package/src/verified-by-gs1.ts +54 -0
- package/test/character-set.test.ts +46 -0
- package/test/creator.test.ts +5 -5
- package/test/data/gcpprefixformatlist-1.json +662625 -0
- package/test/data/gcpprefixformatlist-2.json +735431 -0
- package/test/gcp-length.test.ts +344 -0
- package/test/gtin-creator.ts +4 -4
- package/test/gtin-validator.test.ts +205 -113
- package/test/gtin-validator.ts +30 -0
- package/test/identifier-creator.ts +6 -6
- package/test/non-numeric-identifier-creator.ts +0 -8
- package/test/non-serializable-numeric-identifier-creator.ts +4 -54
- package/test/numeric-identifier-creator.ts +4 -4
- package/test/prefix-manager.test.ts +5 -5
- package/test/serializable-numeric-identifier-creator.ts +32 -19
- package/test/validator.test.ts +6 -6
- package/test/variable-measure-rcn.test.ts +63 -68
- package/test/verified-by-gs1.test.ts +55 -0
- package/tsconfig-src.json +7 -1
- package/tsconfig-src.tsbuildinfo +1 -0
- package/tsconfig-tsup.json +7 -0
package/src/prefix-validator.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
134
|
-
*
|
|
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,
|
|
137
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
validation = GS1_8_PREFIX_VALIDATION;
|
|
175
176
|
break;
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
|
|
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,
|
|
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(
|
|
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 =
|
|
213
|
+
gs1CompanyPrefix = `0${prefix}`;
|
|
218
214
|
break;
|
|
219
215
|
|
|
220
216
|
case PrefixTypes.GS18Prefix:
|
|
221
|
-
gs1CompanyPrefix =
|
|
217
|
+
gs1CompanyPrefix = `00000${prefix}`;
|
|
222
218
|
break;
|
|
223
219
|
}
|
|
224
220
|
|
|
@@ -8,6 +8,21 @@ import { NonGTINNumericIdentifierValidator } from "./non-gtin-numeric-identifier
|
|
|
8
8
|
import type { SerializableNumericIdentifierDescriptor } from "./serializable-numeric-identifier-descriptor.js";
|
|
9
9
|
import type { SerializableNumericIdentifierType } from "./serializable-numeric-identifier-type.js";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Serializable numeric identifier split.
|
|
13
|
+
*/
|
|
14
|
+
export interface SerializableNumericIdentifierSplit {
|
|
15
|
+
/**
|
|
16
|
+
* Base identifier.
|
|
17
|
+
*/
|
|
18
|
+
baseIdentifier: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Serial component.
|
|
22
|
+
*/
|
|
23
|
+
serialComponent: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
/**
|
|
12
27
|
* Serializable numeric identifier validator. Validates both serialized and non-serialized forms of numeric identifiers
|
|
13
28
|
* that support serialization.
|
|
@@ -94,4 +109,25 @@ export class SerializableNumericIdentifierValidator extends NonGTINNumericIdenti
|
|
|
94
109
|
this.serialComponentCreator.validate(identifier.substring(this.length), this.#serialComponentValidation);
|
|
95
110
|
}
|
|
96
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Split a serializable numeric identifier into its component parts.
|
|
115
|
+
*
|
|
116
|
+
* @param identifier
|
|
117
|
+
* Identifier.
|
|
118
|
+
*
|
|
119
|
+
* @param validation
|
|
120
|
+
* Identifier validation parameters.
|
|
121
|
+
*
|
|
122
|
+
* @returns
|
|
123
|
+
* Base identifier and serial component (possibly empty) as object.
|
|
124
|
+
*/
|
|
125
|
+
split(identifier: string, validation?: IdentifierValidation): SerializableNumericIdentifierSplit {
|
|
126
|
+
this.validate(identifier, validation);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
baseIdentifier: identifier.substring(0, this.length),
|
|
130
|
+
serialComponent: identifier.substring(this.length)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
97
133
|
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { NUMERIC_CREATOR } from "@aidc-toolkit/utility";
|
|
2
|
+
import { checkDigit, hasValidCheckDigit, isValidPriceOrWeightCheckDigit, priceOrWeightCheckDigit } from "./check.js";
|
|
3
|
+
import { i18nextGS1 } from "./locale/i18n.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Restricted Circulation Number reference.
|
|
7
|
+
*/
|
|
8
|
+
export interface RCNReference {
|
|
9
|
+
/**
|
|
10
|
+
* Item reference.
|
|
11
|
+
*/
|
|
12
|
+
itemReference: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Price or weight (whole number only).
|
|
16
|
+
*/
|
|
17
|
+
priceOrWeight: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Variable measure trade item support functions.
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-extraneous-class -- Wrapper for future functionality.
|
|
24
|
+
export class VariableMeasure {
|
|
25
|
+
/**
|
|
26
|
+
* Parse a Restricted Circulation Number (RCN) using a variable measure trade item format. The format is a 12- or
|
|
27
|
+
* 13-character string (for RCN-12 or RCN-13 respectively), containing the following:
|
|
28
|
+
*
|
|
29
|
+
* - '2' - The first character of the RCN.
|
|
30
|
+
* - '0'-'9' - The second character of the RCN (RCN-13 only).
|
|
31
|
+
* - 'I' - One or more, in sequence, for the item reference.
|
|
32
|
+
* - 'P' - One or more, in sequence, for the price or weight.
|
|
33
|
+
* - 'V' - Zero or one, for the price or weight check digit.
|
|
34
|
+
* - 'C' - The check digit of the entire RCN.
|
|
35
|
+
*
|
|
36
|
+
* The 'I', 'P', and 'V' formats may be in any order.
|
|
37
|
+
*
|
|
38
|
+
* Some examples:
|
|
39
|
+
*
|
|
40
|
+
* - `2IIIIIVPPPPC` - RCN-12 with a five-digit item reference, a price or weight check digit, and a four-digit price
|
|
41
|
+
* or weight.
|
|
42
|
+
* - `23IIIIVPPPPPC` - RCN-13 with a four-digit item reference, a price or weight check digit, and a five-digit price
|
|
43
|
+
* or weight.
|
|
44
|
+
* - `2IIIIIIPPPPC` - RCN-12 with a six-digit item reference and a four-digit price or eight.
|
|
45
|
+
* - `29IIIIIPPPPPC` - RCN-13 with a five-digit item reference and a five-digit price or weight.
|
|
46
|
+
*
|
|
47
|
+
* @param format
|
|
48
|
+
* Format.
|
|
49
|
+
*
|
|
50
|
+
* @param rcn
|
|
51
|
+
* RCN.
|
|
52
|
+
*
|
|
53
|
+
* @returns
|
|
54
|
+
* RCN reference.
|
|
55
|
+
*/
|
|
56
|
+
static parseRCN(format: string, rcn: string): RCNReference {
|
|
57
|
+
const formatLength = format.length;
|
|
58
|
+
|
|
59
|
+
if (rcn.length !== formatLength) {
|
|
60
|
+
throw new RangeError(i18nextGS1.t("Identifier.invalidRCNLength"));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let validFormat = formatLength === 12 || formatLength === 13;
|
|
64
|
+
let validRCNPrefix = true;
|
|
65
|
+
|
|
66
|
+
let buildingItemReference = false;
|
|
67
|
+
let itemReference = "";
|
|
68
|
+
|
|
69
|
+
let buildingPriceOrWeight = false;
|
|
70
|
+
let priceOrWeight = "";
|
|
71
|
+
|
|
72
|
+
let priceOrWeightCheckDigit = "";
|
|
73
|
+
|
|
74
|
+
for (let index = 0; validFormat && index < formatLength; index++) {
|
|
75
|
+
const formatChar = format.charAt(index);
|
|
76
|
+
const rcnChar = rcn.charAt(index);
|
|
77
|
+
|
|
78
|
+
if (index === 0) {
|
|
79
|
+
validFormat = formatChar === "2";
|
|
80
|
+
validRCNPrefix = rcnChar === "2";
|
|
81
|
+
} else if (formatLength === 13 && index === 1) {
|
|
82
|
+
validFormat = NUMERIC_CREATOR.characterIndex(formatChar) !== undefined;
|
|
83
|
+
validRCNPrefix = rcnChar === formatChar;
|
|
84
|
+
} else if (index === formatLength - 1) {
|
|
85
|
+
validFormat = formatChar === "C";
|
|
86
|
+
} else {
|
|
87
|
+
switch (formatChar) {
|
|
88
|
+
case "I":
|
|
89
|
+
if (!buildingItemReference) {
|
|
90
|
+
// Item reference can't appear more than once.
|
|
91
|
+
validFormat = itemReference === "";
|
|
92
|
+
|
|
93
|
+
buildingItemReference = true;
|
|
94
|
+
buildingPriceOrWeight = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
itemReference += rcnChar;
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case "P":
|
|
101
|
+
if (!buildingPriceOrWeight) {
|
|
102
|
+
// Price or weight can't appear more than once.
|
|
103
|
+
validFormat = priceOrWeight === "";
|
|
104
|
+
|
|
105
|
+
buildingPriceOrWeight = true;
|
|
106
|
+
buildingItemReference = false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
priceOrWeight += rcnChar;
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case "V":
|
|
113
|
+
// Price or weight check digit can't appear more than once.
|
|
114
|
+
validFormat = priceOrWeightCheckDigit === "";
|
|
115
|
+
|
|
116
|
+
buildingItemReference = false;
|
|
117
|
+
buildingPriceOrWeight = false;
|
|
118
|
+
|
|
119
|
+
priceOrWeightCheckDigit = rcnChar;
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
validFormat = false;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
validFormat &&= itemReference !== "" && priceOrWeight !== "";
|
|
130
|
+
|
|
131
|
+
if (!validFormat) {
|
|
132
|
+
throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasureRCNFormat"));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!validRCNPrefix) {
|
|
136
|
+
throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasureRCNPrefix"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (priceOrWeightCheckDigit !== "" && !isValidPriceOrWeightCheckDigit(priceOrWeight, priceOrWeightCheckDigit)) {
|
|
140
|
+
throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasurePriceOrWeight"));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!hasValidCheckDigit(rcn)) {
|
|
144
|
+
throw new RangeError(i18nextGS1.t("Identifier.invalidCheckDigit"));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
itemReference: Number(itemReference),
|
|
149
|
+
priceOrWeight: Number(priceOrWeight)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create a Restricted Circulation Number (RCN) using a variable measure trade item format. See {@linkcode parseRCN}
|
|
155
|
+
* for format details.
|
|
156
|
+
*
|
|
157
|
+
* @param format
|
|
158
|
+
* Format.
|
|
159
|
+
*
|
|
160
|
+
* @param itemReference
|
|
161
|
+
* Item reference.
|
|
162
|
+
*
|
|
163
|
+
* @param priceOrWeight
|
|
164
|
+
* Price or weight (whole number only).
|
|
165
|
+
*
|
|
166
|
+
* @returns
|
|
167
|
+
* RCN-12 or RCN-13.
|
|
168
|
+
*/
|
|
169
|
+
static createRCN(format: string, itemReference: number, priceOrWeight: number): string {
|
|
170
|
+
const formatLength = format.length;
|
|
171
|
+
|
|
172
|
+
let validFormat = formatLength === 12 || formatLength === 13;
|
|
173
|
+
|
|
174
|
+
let rcnPrefix = "";
|
|
175
|
+
|
|
176
|
+
let buildingItemReference = false;
|
|
177
|
+
let itemReferenceString = "";
|
|
178
|
+
let itemReferenceLength = 0;
|
|
179
|
+
|
|
180
|
+
let buildingPriceOrWeight = false;
|
|
181
|
+
let priceOrWeightString = "";
|
|
182
|
+
let priceOrWeightLength = 0;
|
|
183
|
+
|
|
184
|
+
let calculatePriceOrWeightCheckDigit = false;
|
|
185
|
+
|
|
186
|
+
// RCN may be built in almost any order, so defer to builders that will be in ordered array.
|
|
187
|
+
const rcnPrefixBuilder = (partialRCN: string): string => partialRCN + rcnPrefix;
|
|
188
|
+
const itemReferenceBuilder = (partialRCN: string): string => partialRCN + itemReferenceString;
|
|
189
|
+
const priceOrWeightBuilder = (partialRCN: string): string => partialRCN + priceOrWeightString;
|
|
190
|
+
const priceOrWeightCheckDigitBuilder = (partialRCN: string): string => partialRCN + priceOrWeightCheckDigit(priceOrWeightString);
|
|
191
|
+
const checkDigitBuilder = (partialRCN: string): string => partialRCN + checkDigit(partialRCN);
|
|
192
|
+
|
|
193
|
+
const rcnBuilders = [rcnPrefixBuilder];
|
|
194
|
+
|
|
195
|
+
for (let index = 0; validFormat && index < formatLength; index++) {
|
|
196
|
+
const formatChar = format.charAt(index);
|
|
197
|
+
|
|
198
|
+
if (index === 0) {
|
|
199
|
+
validFormat = formatChar === "2";
|
|
200
|
+
rcnPrefix = formatChar;
|
|
201
|
+
} else if (formatLength === 13 && index === 1) {
|
|
202
|
+
validFormat = NUMERIC_CREATOR.characterIndex(formatChar) !== undefined;
|
|
203
|
+
rcnPrefix += formatChar;
|
|
204
|
+
} else if (index === formatLength - 1) {
|
|
205
|
+
validFormat = formatChar === "C";
|
|
206
|
+
} else {
|
|
207
|
+
switch (formatChar) {
|
|
208
|
+
case "I":
|
|
209
|
+
if (!buildingItemReference) {
|
|
210
|
+
// Item reference can't appear more than once.
|
|
211
|
+
validFormat = itemReferenceLength === 0;
|
|
212
|
+
|
|
213
|
+
buildingItemReference = true;
|
|
214
|
+
buildingPriceOrWeight = false;
|
|
215
|
+
|
|
216
|
+
rcnBuilders.push(itemReferenceBuilder);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
itemReferenceLength++;
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case "P":
|
|
223
|
+
if (!buildingPriceOrWeight) {
|
|
224
|
+
// Price or weight can't appear more than once.
|
|
225
|
+
validFormat = priceOrWeightLength === 0;
|
|
226
|
+
|
|
227
|
+
buildingPriceOrWeight = true;
|
|
228
|
+
buildingItemReference = false;
|
|
229
|
+
|
|
230
|
+
rcnBuilders.push(priceOrWeightBuilder);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
priceOrWeightLength++;
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case "V":
|
|
237
|
+
// Price or weight check digit can't appear more than once.
|
|
238
|
+
validFormat = !calculatePriceOrWeightCheckDigit;
|
|
239
|
+
|
|
240
|
+
buildingItemReference = false;
|
|
241
|
+
buildingPriceOrWeight = false;
|
|
242
|
+
|
|
243
|
+
calculatePriceOrWeightCheckDigit = true;
|
|
244
|
+
|
|
245
|
+
rcnBuilders.push(priceOrWeightCheckDigitBuilder);
|
|
246
|
+
break;
|
|
247
|
+
|
|
248
|
+
default:
|
|
249
|
+
validFormat = false;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
validFormat &&= itemReferenceLength !== 0 && priceOrWeightLength !== 0;
|
|
256
|
+
|
|
257
|
+
if (!validFormat) {
|
|
258
|
+
throw new RangeError(i18nextGS1.t("Identifier.invalidVariableMeasureRCNFormat"));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
itemReferenceString = NUMERIC_CREATOR.create(itemReferenceLength, itemReference);
|
|
262
|
+
priceOrWeightString = NUMERIC_CREATOR.create(priceOrWeightLength, priceOrWeight);
|
|
263
|
+
|
|
264
|
+
rcnBuilders.push(checkDigitBuilder);
|
|
265
|
+
|
|
266
|
+
return rcnBuilders.reduce((partialRCN, rcnBuilder) => rcnBuilder(partialRCN), "");
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Hyperlink } from "@aidc-toolkit/core";
|
|
2
|
+
import { GTINValidator } from "./gtin-validator.js";
|
|
3
|
+
import { type IdentifierType, IdentifierTypes } from "./identifier-type.js";
|
|
4
|
+
import { IdentifierValidators } from "./identifier-validators.js";
|
|
5
|
+
|
|
6
|
+
const VERIFIED_BY_GS1_REFERENCE_BASE = "https://www.gs1.org/services/verified-by-gs1/results?";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a Verified by GS1 hyperlink.
|
|
10
|
+
*
|
|
11
|
+
* @param identifierType
|
|
12
|
+
* Identifier type.
|
|
13
|
+
*
|
|
14
|
+
* @param identifier
|
|
15
|
+
* Identifier.
|
|
16
|
+
*
|
|
17
|
+
* @param text
|
|
18
|
+
* Text for hyperlink. If not provided, the identifier is used.
|
|
19
|
+
*
|
|
20
|
+
* @param details
|
|
21
|
+
* Details to display when hovering over hyperlink.
|
|
22
|
+
*
|
|
23
|
+
* @returns
|
|
24
|
+
* Verified by GS1 hyperlink.
|
|
25
|
+
*/
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-useless-default-assignment -- Undefined is necessary to allow bypass of text.
|
|
27
|
+
export function verifiedByGS1(identifierType: IdentifierType, identifier: string, text: string | undefined = undefined, details?: string): Hyperlink {
|
|
28
|
+
let normalizedIdentifier: string;
|
|
29
|
+
let useKeyTypeParameter: boolean;
|
|
30
|
+
|
|
31
|
+
if (identifierType === IdentifierTypes.GTIN) {
|
|
32
|
+
// Normalization will validate resulting GTIN.
|
|
33
|
+
normalizedIdentifier = GTINValidator.normalize(identifier);
|
|
34
|
+
|
|
35
|
+
useKeyTypeParameter = true;
|
|
36
|
+
} else {
|
|
37
|
+
const identifierValidator = IdentifierValidators[identifierType];
|
|
38
|
+
|
|
39
|
+
identifierValidator.validate(identifier);
|
|
40
|
+
|
|
41
|
+
normalizedIdentifier = identifier;
|
|
42
|
+
useKeyTypeParameter = identifierType === IdentifierTypes.GLN;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const lowerCaseIdentifierType = identifierType.toLowerCase();
|
|
46
|
+
|
|
47
|
+
const reference = useKeyTypeParameter ? `${VERIFIED_BY_GS1_REFERENCE_BASE}${lowerCaseIdentifierType}=${normalizedIdentifier}` : `${VERIFIED_BY_GS1_REFERENCE_BASE}key=${normalizedIdentifier}&key_type=${lowerCaseIdentifierType}`;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
reference,
|
|
51
|
+
text: text ?? identifier,
|
|
52
|
+
details
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { AI64_VALIDATOR } from "../src/index.js";
|
|
3
|
+
|
|
4
|
+
describe("AI 64 character set validator", () => {
|
|
5
|
+
test("Character set", () => {
|
|
6
|
+
expect(() => {
|
|
7
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz");
|
|
8
|
+
}).not.toThrow(RangeError);
|
|
9
|
+
|
|
10
|
+
expect(() => {
|
|
11
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxy");
|
|
12
|
+
}).toThrow("Length 63 must be a multiple of 4");
|
|
13
|
+
|
|
14
|
+
expect(() => {
|
|
15
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwx");
|
|
16
|
+
}).toThrow("Length 62 must be a multiple of 4");
|
|
17
|
+
|
|
18
|
+
expect(() => {
|
|
19
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvw");
|
|
20
|
+
}).toThrow("Length 61 must be a multiple of 4");
|
|
21
|
+
|
|
22
|
+
expect(() => {
|
|
23
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzABC=");
|
|
24
|
+
}).not.toThrow(RangeError);
|
|
25
|
+
|
|
26
|
+
expect(() => {
|
|
27
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzAB==");
|
|
28
|
+
}).not.toThrow(RangeError);
|
|
29
|
+
|
|
30
|
+
expect(() => {
|
|
31
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzA===");
|
|
32
|
+
}).toThrow("Invalid character '=' at position 65");
|
|
33
|
+
|
|
34
|
+
expect(() => {
|
|
35
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz====");
|
|
36
|
+
}).toThrow("Invalid character '=' at position 64");
|
|
37
|
+
|
|
38
|
+
expect(() => {
|
|
39
|
+
AI64_VALIDATOR.validate("=-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxy");
|
|
40
|
+
}).toThrow("Invalid character '=' at position 0");
|
|
41
|
+
|
|
42
|
+
expect(() => {
|
|
43
|
+
AI64_VALIDATOR.validate("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ=_abcdefghijklmnopqrstuvwxy");
|
|
44
|
+
}).toThrow("Invalid character '=' at position 37");
|
|
45
|
+
});
|
|
46
|
+
});
|
package/test/creator.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe } from "vitest";
|
|
2
2
|
import { PrefixManager, PrefixTypes } from "../src/index.js";
|
|
3
3
|
import { testGTINCreator } from "./gtin-creator.js";
|
|
4
|
-
import {
|
|
4
|
+
import { testNonSerializableNumericIdentifierCreator } from "./non-serializable-numeric-identifier-creator.js";
|
|
5
5
|
import { testNonNumericIdentifierCreator } from "./non-numeric-identifier-creator.js";
|
|
6
6
|
import { testSerializableNumericIdentifierCreator } from "./serializable-numeric-identifier-creator.js";
|
|
7
7
|
|
|
@@ -14,14 +14,14 @@ describe("Creators", () => {
|
|
|
14
14
|
const shortPrefixManager = PrefixManager.get(PrefixTypes.GS1CompanyPrefix, "952123456");
|
|
15
15
|
const longPrefixManager = PrefixManager.get(PrefixTypes.GS1CompanyPrefix, "952123456789");
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
testNonSerializableNumericIdentifierCreator(shortPrefixManager.glnCreator);
|
|
18
|
+
testNonSerializableNumericIdentifierCreator(longPrefixManager.ssccCreator);
|
|
19
19
|
testSerializableNumericIdentifierCreator(shortPrefixManager.graiCreator);
|
|
20
20
|
testNonNumericIdentifierCreator(shortPrefixManager.giaiCreator);
|
|
21
|
-
|
|
21
|
+
testNonSerializableNumericIdentifierCreator(longPrefixManager.gsrnCreator);
|
|
22
22
|
testSerializableNumericIdentifierCreator(shortPrefixManager.gdtiCreator);
|
|
23
23
|
testNonNumericIdentifierCreator(shortPrefixManager.gincCreator);
|
|
24
|
-
|
|
24
|
+
testNonSerializableNumericIdentifierCreator(longPrefixManager.gsinCreator);
|
|
25
25
|
testSerializableNumericIdentifierCreator(shortPrefixManager.gcnCreator);
|
|
26
26
|
testNonNumericIdentifierCreator(shortPrefixManager.cpidCreator);
|
|
27
27
|
testNonNumericIdentifierCreator(shortPrefixManager.gmnCreator);
|