@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/check.ts ADDED
@@ -0,0 +1,232 @@
1
+ import { NUMERIC_CREATOR } from "@aidc-toolkit/utility";
2
+ import { AI82_CREATOR } from "./character_set.js";
3
+ import i18next, { gs1NS } from "./locale/i18n.js";
4
+
5
+ /**
6
+ * Results of multiplying digits by 3.
7
+ */
8
+ const THREE_WEIGHT_RESULTS: readonly number[] = [
9
+ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27
10
+ ];
11
+
12
+ /**
13
+ * Results of multiplying digits by 2, subtracting tens digit, and extracting units digit.
14
+ */
15
+ const TWO_MINUS_WEIGHT_RESULTS: readonly number[] = [
16
+ 0, 2, 4, 6, 8, 9, 1, 3, 5, 7
17
+ ];
18
+
19
+ /**
20
+ * Results of multiplying digits by 5, adding tens digit, and extracting units digit.
21
+ */
22
+ const FIVE_PLUS_WEIGHT_RESULTS: readonly number[] = [
23
+ 0, 5, 1, 6, 2, 7, 3, 8, 4, 9
24
+ ];
25
+
26
+ /**
27
+ * Results of multiplying digits by 5, subtracting tens digit, and extracting units digit.
28
+ */
29
+ const FIVE_MINUS_WEIGHT_RESULTS: readonly number[] = [
30
+ 0, 5, 9, 4, 8, 3, 7, 2, 6, 1
31
+ ];
32
+
33
+ /**
34
+ * Inverse mapping of FIVE_MINUS_WEIGHT_RESULTS.
35
+ */
36
+ const INVERSE_FIVE_MINUS_WEIGHT_RESULTS: readonly number[] = [
37
+ 0, 9, 7, 5, 3, 1, 8, 6, 4, 2
38
+ ];
39
+
40
+ /**
41
+ * Calculate the check digit sum for a numeric string as per section 7.9.1 of the {@link https://www.gs1.org/genspecs |
42
+ * GS1 General Specifications}.
43
+ *
44
+ * @param exchangeWeights
45
+ * If true, start the weights at 1 instead of 3 on the right.
46
+ *
47
+ * @param s
48
+ * Numeric string.
49
+ *
50
+ * @returns
51
+ * Accumulated sum of each digit multiplied by the weight at its position.
52
+ */
53
+ export function checkDigitSum(exchangeWeights: boolean, s: string): number {
54
+ // Initial setting will be the opposite of that used for first character because it gets flipped before being used.
55
+ let weight3 = (s.length + Number(exchangeWeights)) % 2 === 0;
56
+
57
+ // Calculate sum of each character value multiplied by the weight at its position.
58
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
59
+ return NUMERIC_CREATOR.characterIndexes(s).reduce((accumulator, characterIndex, index) => {
60
+ if (characterIndex === undefined) {
61
+ throw new RangeError(`Invalid character '${s.charAt(index)}' at position ${index + 1}`);
62
+ }
63
+
64
+ weight3 = !weight3;
65
+
66
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
67
+ return accumulator! + (weight3 ? THREE_WEIGHT_RESULTS[characterIndex] : characterIndex);
68
+ }, 0)!;
69
+ }
70
+
71
+ /**
72
+ * Calculate the check digit for a numeric string as per section 7.9.1 of the {@link https://www.gs1.org/genspecs | GS1
73
+ * General Specifications}.
74
+ *
75
+ * @param s
76
+ * Numeric string.
77
+ *
78
+ * @returns
79
+ * Check digit 0-9 as a string.
80
+ */
81
+ export function checkDigit(s: string): string {
82
+ // Check digit is the difference from the equal or next of multiple of 10.
83
+ return NUMERIC_CREATOR.character(9 - (checkDigitSum(false, s) + 9) % 10);
84
+ }
85
+
86
+ /**
87
+ * Determine if a numeric string has a valid check digit.
88
+ *
89
+ * @param s
90
+ * Numeric string with check digit.
91
+ *
92
+ * @returns
93
+ * True if the check digit is valid.
94
+ */
95
+ export function hasValidCheckDigit(s: string): boolean {
96
+ // Check digit is valid if the check digit sum with the weights exchanged is a multiple of 10.
97
+ return checkDigitSum(true, s) % 10 === 0;
98
+ }
99
+
100
+ /**
101
+ * Calculate the price/weight sum for a numeric string.
102
+ *
103
+ * @param weightsResults
104
+ * Array of weight results arrays to apply to each digit.
105
+ *
106
+ * @param s
107
+ * Numeric string the same length as the weightsResults array.
108
+ *
109
+ * @returns
110
+ * Accumulated sum of the weight result for each digit at the digit's position.
111
+ *
112
+ * @throws RangeError
113
+ */
114
+ function priceWeightSum(weightsResults: ReadonlyArray<readonly number[]>, s: string): number {
115
+ if (s.length !== weightsResults.length) {
116
+ throw new RangeError(`String for price or weight sum must be exactly ${weightsResults.length} characters`);
117
+ }
118
+
119
+ // The value of each character is its index in the character set.
120
+ const characterIndexes = NUMERIC_CREATOR.characterIndexes(s);
121
+
122
+ // Calculate sum of each weight result for each digit at its position.
123
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
124
+ return characterIndexes.reduce((accumulator, characterIndex, index) => {
125
+ if (characterIndex === undefined) {
126
+ throw new RangeError(`Invalid character '${s.charAt(index)}' at position ${index + 1}`);
127
+ }
128
+
129
+ // Add the weight result of the character index to the accumulator.
130
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
131
+ return accumulator! + weightsResults[index][characterIndex];
132
+ }, 0)!;
133
+ }
134
+
135
+ /**
136
+ * Calculate the price/weight check digit for a four-digit numeric string as per section 7.9.3 of the {@link
137
+ * https://www.gs1.org/genspecs | GS1 General Specifications}.
138
+ *
139
+ * @param s
140
+ * Numeric string exactly four characters long.
141
+ *
142
+ * @returns
143
+ * Check digit 0-9 as a string.
144
+ */
145
+ export function fourDigitPriceWeightCheckDigit(s: string): string {
146
+ return NUMERIC_CREATOR.character(priceWeightSum([TWO_MINUS_WEIGHT_RESULTS, TWO_MINUS_WEIGHT_RESULTS, THREE_WEIGHT_RESULTS, FIVE_MINUS_WEIGHT_RESULTS], s) * 3 % 10);
147
+ }
148
+
149
+ /**
150
+ * Calculate the price/weight check digit for a five-digit numeric string as per section 7.9.3 of the {@link
151
+ * https://www.gs1.org/genspecs | GS1 General Specifications}.
152
+ *
153
+ * @param s
154
+ * Numeric string exactly five characters long.
155
+ *
156
+ * @returns
157
+ * Check digit 0-9 as a string.
158
+ */
159
+ export function fiveDigitPriceWeightCheckDigit(s: string): string {
160
+ return NUMERIC_CREATOR.character(INVERSE_FIVE_MINUS_WEIGHT_RESULTS[9 - (priceWeightSum([FIVE_PLUS_WEIGHT_RESULTS, TWO_MINUS_WEIGHT_RESULTS, FIVE_MINUS_WEIGHT_RESULTS, FIVE_PLUS_WEIGHT_RESULTS, TWO_MINUS_WEIGHT_RESULTS], s) + 9) % 10]);
161
+ }
162
+
163
+ /**
164
+ * Weights for check character pair calculation.
165
+ */
166
+ const CHECK_CHARACTER_WEIGHTS = [
167
+ 107, 103, 101, 97, 89, 83, 79, 73, 71, 67, 61, 59, 53, 47, 43, 41, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2
168
+ ];
169
+
170
+ /**
171
+ * Characters used to build check character pair.
172
+ */
173
+ const CHECK_CHARACTERS = [
174
+ "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
175
+ "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
176
+ ];
177
+
178
+ /**
179
+ * Calculate the check character for a GS1 AI encodable character set 82 string as per section 7.9.5 of the {@link
180
+ * https://www.gs1.org/genspecs | GS1 General Specifications}.
181
+ *
182
+ * @param s
183
+ * GS1 AI encodable character set 82 string.
184
+ *
185
+ * @returns
186
+ * Check character pair.
187
+ *
188
+ * @throws RangeError
189
+ */
190
+ export function checkCharacterPair(s: string): string {
191
+ // Weights are applied from right to left.
192
+ const weightIndexStart = CHECK_CHARACTER_WEIGHTS.length - s.length;
193
+
194
+ if (weightIndexStart < 0) {
195
+ throw new RangeError(i18next.t("Check.lengthOfStringForCheckCharacterPairMustBeLessThanOrEqualTo", {
196
+ ns: gs1NS,
197
+ length: s.length,
198
+ maximumLength: CHECK_CHARACTER_WEIGHTS.length
199
+ }));
200
+ }
201
+
202
+ // Calculate sum of each character value multiplied by the weight at its position, mod 1021.
203
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
+ const checkCharacterPairSum = AI82_CREATOR.characterIndexes(s).reduce((accumulator, characterIndex, index) => {
205
+ if (characterIndex === undefined) {
206
+ throw new RangeError(`Invalid character '${s.charAt(index)}' at position ${index + 1}`);
207
+ }
208
+
209
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
210
+ return accumulator! + characterIndex * CHECK_CHARACTER_WEIGHTS[weightIndexStart + index];
211
+ }, 0)! % 1021;
212
+
213
+ const checkCharacterPairSumMod32 = checkCharacterPairSum % 32;
214
+
215
+ // Check character pair is the concatenation of the character at position sum div 32 and the character at the position sum mod 32.
216
+ return CHECK_CHARACTERS[(checkCharacterPairSum - checkCharacterPairSumMod32) / 32] + CHECK_CHARACTERS[checkCharacterPairSumMod32];
217
+ }
218
+
219
+ /**
220
+ * Determine if a GS1 AI encodable character set 82 string has a valid check character pair.
221
+ *
222
+ * @param s
223
+ * GS1 AI encodable character set 82 string with check character pair.
224
+ *
225
+ * @returns
226
+ * True if the check character pair is valid.
227
+ */
228
+ export function hasValidCheckCharacterPair(s: string): boolean {
229
+ const checkCharacterPairIndex = s.length - 2;
230
+
231
+ return checkCharacterPair(s.substring(0, checkCharacterPairIndex)) === s.substring(checkCharacterPairIndex);
232
+ }