@ibiliaze/stringman 3.14.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/seat.d.ts +1 -1
- package/dist/ticket.d.ts +60 -0
- package/dist/ticket.js +70 -21
- package/package.json +2 -2
- package/src/seat.ts +1 -1
- package/src/ticket.ts +82 -24
package/dist/index.d.ts
CHANGED
package/dist/seat.d.ts
CHANGED
package/dist/ticket.d.ts
CHANGED
|
@@ -1,30 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute a Luhn mod-10 check digit for a numeric string.
|
|
3
|
+
* @param digits Numeric string (no separators).
|
|
4
|
+
* @returns Single check digit as a string.
|
|
5
|
+
*/
|
|
1
6
|
export declare const luhn10: (digits: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Compact, stable 2-digit number for a venue or any short code.
|
|
9
|
+
* Uses a small SHA-1 based hash and modulo to fit the requested width.
|
|
10
|
+
*/
|
|
2
11
|
export declare const venueBlockNum: (venueCode: string, len?: number) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Compact, stable 2-digit number for a Fixture/Match ID string.
|
|
14
|
+
* If you pass your DB fixtureId here, you’ll get a short numeric block.
|
|
15
|
+
*/
|
|
16
|
+
export declare const fixtureIdBlockNum: (fixtureId: string, len?: number) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Compact, stable 3-digit number for a specific seat (section + row + col).
|
|
19
|
+
*/
|
|
3
20
|
export declare const seatBlockNum: (sectionId: string, row: number, col: number, len?: number) => string;
|
|
21
|
+
/**
|
|
22
|
+
* 6-digit date/time block derived from local start time.
|
|
23
|
+
* Input format expected: "YYYY-MM-DDThh:mm" (local, no 'Z').
|
|
24
|
+
* Encodes YYMMDDhhmm then reduces modulo 10^len to keep width stable.
|
|
25
|
+
*/
|
|
4
26
|
export declare const fixtureBlockNum: (fixtureLocalIso: string, len?: number) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Cryptographically strong random numeric block of the given length.
|
|
29
|
+
*/
|
|
5
30
|
export declare const rndBlockNum: (len?: number) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Build a numeric ticket code with blocks ordered to match the picture *in reverse*:
|
|
33
|
+
* Random (3) + Sector/Block/Col (3) + Date/Time (6) + Fixture/Match ID (2) + Luhn(1)
|
|
34
|
+
*
|
|
35
|
+
* Default dashed display groups: 3-3-6-2-1
|
|
36
|
+
*
|
|
37
|
+
* Lengths are chosen to keep the overall size at 15 digits (incl. Luhn),
|
|
38
|
+
* which plays nicely with scanners and manual entry.
|
|
39
|
+
*/
|
|
6
40
|
export declare const buildTicketCodeNumeric: (args: {
|
|
41
|
+
/** Human/ops venue short code (e.g. "SB"). Used only if fixtureId is not provided. */
|
|
7
42
|
venueCode: string;
|
|
43
|
+
/** Local fixture start time, "YYYY-MM-DDThh:mm" (no 'Z'). */
|
|
8
44
|
fixtureLocalIso: string;
|
|
45
|
+
/** Section identifier as used in your DB. */
|
|
9
46
|
sectionId: string;
|
|
47
|
+
/** Seat row number. */
|
|
10
48
|
row: number;
|
|
49
|
+
/** Seat column/number. */
|
|
11
50
|
col: number;
|
|
51
|
+
/** Optional: your DB fixtureId. If present, becomes the 2-digit “Fixture/Match ID” block. */
|
|
52
|
+
fixtureId?: string;
|
|
53
|
+
/** Random block length (default 3). */
|
|
12
54
|
rndLen?: number;
|
|
55
|
+
/** Show dashes for readability (default true). */
|
|
13
56
|
dashed?: boolean;
|
|
14
57
|
}) => string;
|
|
58
|
+
/**
|
|
59
|
+
* Validate a code built by buildTicketCodeNumeric (dashes optional).
|
|
60
|
+
* Checks: numeric, correct total length (15), and Luhn checksum.
|
|
61
|
+
*/
|
|
15
62
|
export declare const validateTicketCodeNumeric: (code: string) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* A stable, human-readable internal ID for a seat within a fixture.
|
|
65
|
+
* (Not used in the printed/scanned code — just handy elsewhere.)
|
|
66
|
+
*/
|
|
16
67
|
export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
|
|
17
68
|
fixtureId: string;
|
|
18
69
|
sectionName: string;
|
|
19
70
|
seatName: string;
|
|
20
71
|
}) => string;
|
|
21
72
|
export declare const buildTicketCode: (args: {
|
|
73
|
+
/** Human/ops venue short code (e.g. "SB"). Used only if fixtureId is not provided. */
|
|
22
74
|
venueCode: string;
|
|
75
|
+
/** Local fixture start time, "YYYY-MM-DDThh:mm" (no 'Z'). */
|
|
23
76
|
fixtureLocalIso: string;
|
|
77
|
+
/** Section identifier as used in your DB. */
|
|
24
78
|
sectionId: string;
|
|
79
|
+
/** Seat row number. */
|
|
25
80
|
row: number;
|
|
81
|
+
/** Seat column/number. */
|
|
26
82
|
col: number;
|
|
83
|
+
/** Optional: your DB fixtureId. If present, becomes the 2-digit “Fixture/Match ID” block. */
|
|
84
|
+
fixtureId?: string;
|
|
85
|
+
/** Random block length (default 3). */
|
|
27
86
|
rndLen?: number;
|
|
87
|
+
/** Show dashes for readability (default true). */
|
|
28
88
|
dashed?: boolean;
|
|
29
89
|
}) => string;
|
|
30
90
|
export declare const validateTicketCode: (code: string) => boolean;
|
package/dist/ticket.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateTicketCode = exports.buildTicketCode = exports.getTicketId = exports.validateTicketCodeNumeric = exports.buildTicketCodeNumeric = exports.rndBlockNum = exports.fixtureBlockNum = exports.seatBlockNum = exports.venueBlockNum = exports.luhn10 = void 0;
|
|
3
|
+
exports.validateTicketCode = exports.buildTicketCode = exports.getTicketId = exports.validateTicketCodeNumeric = exports.buildTicketCodeNumeric = exports.rndBlockNum = exports.fixtureBlockNum = exports.seatBlockNum = exports.fixtureIdBlockNum = exports.venueBlockNum = exports.luhn10 = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Left-pad a positive integer with zeros to a fixed width.
|
|
7
|
+
*/
|
|
6
8
|
const pad = (n, len) => n.toString().padStart(len, '0');
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Compute a Luhn mod-10 check digit for a numeric string.
|
|
11
|
+
* @param digits Numeric string (no separators).
|
|
12
|
+
* @returns Single check digit as a string.
|
|
13
|
+
*/
|
|
8
14
|
const luhn10 = (digits) => {
|
|
9
15
|
let sum = 0;
|
|
10
16
|
let dbl = false;
|
|
@@ -22,11 +28,13 @@ const luhn10 = (digits) => {
|
|
|
22
28
|
return String(check);
|
|
23
29
|
};
|
|
24
30
|
exports.luhn10 = luhn10;
|
|
25
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Compact, stable 2-digit number for a venue or any short code.
|
|
33
|
+
* Uses a small SHA-1 based hash and modulo to fit the requested width.
|
|
34
|
+
*/
|
|
26
35
|
const venueBlockNum = (venueCode, len = 2) => {
|
|
27
36
|
try {
|
|
28
|
-
|
|
29
|
-
const h = (0, node_crypto_1.createHash)('sha1').update(venueCode.toUpperCase()).digest(); // 20 bytes
|
|
37
|
+
const h = (0, node_crypto_1.createHash)('sha1').update(venueCode.toUpperCase()).digest();
|
|
30
38
|
const n = ((h[0] << 8) | h[1]) % 10 ** len;
|
|
31
39
|
return pad(n, len);
|
|
32
40
|
}
|
|
@@ -35,9 +43,26 @@ const venueBlockNum = (venueCode, len = 2) => {
|
|
|
35
43
|
}
|
|
36
44
|
};
|
|
37
45
|
exports.venueBlockNum = venueBlockNum;
|
|
46
|
+
/**
|
|
47
|
+
* Compact, stable 2-digit number for a Fixture/Match ID string.
|
|
48
|
+
* If you pass your DB fixtureId here, you’ll get a short numeric block.
|
|
49
|
+
*/
|
|
50
|
+
const fixtureIdBlockNum = (fixtureId, len = 2) => {
|
|
51
|
+
try {
|
|
52
|
+
const h = (0, node_crypto_1.createHash)('sha1').update(fixtureId).digest();
|
|
53
|
+
const n = ((h[0] << 8) | h[1]) % 10 ** len;
|
|
54
|
+
return pad(n, len);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return ''.padStart(len, '0');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
exports.fixtureIdBlockNum = fixtureIdBlockNum;
|
|
61
|
+
/**
|
|
62
|
+
* Compact, stable 3-digit number for a specific seat (section + row + col).
|
|
63
|
+
*/
|
|
38
64
|
const seatBlockNum = (sectionId, row, col, len = 3) => {
|
|
39
65
|
try {
|
|
40
|
-
// Hash -> 16-bit -> modulo 10^len
|
|
41
66
|
const h = (0, node_crypto_1.createHash)('sha1').update(`${sectionId}:${row}:${col}`).digest();
|
|
42
67
|
const n16 = (h[0] << 8) | h[1];
|
|
43
68
|
const n = n16 % 10 ** len;
|
|
@@ -48,14 +73,17 @@ const seatBlockNum = (sectionId, row, col, len = 3) => {
|
|
|
48
73
|
}
|
|
49
74
|
};
|
|
50
75
|
exports.seatBlockNum = seatBlockNum;
|
|
76
|
+
/**
|
|
77
|
+
* 6-digit date/time block derived from local start time.
|
|
78
|
+
* Input format expected: "YYYY-MM-DDThh:mm" (local, no 'Z').
|
|
79
|
+
* Encodes YYMMDDhhmm then reduces modulo 10^len to keep width stable.
|
|
80
|
+
*/
|
|
51
81
|
const fixtureBlockNum = (fixtureLocalIso, len = 6) => {
|
|
52
82
|
try {
|
|
53
|
-
// Expect "YYYY-MM-DDThh:mm" (local, no Z).
|
|
54
83
|
const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
|
|
55
84
|
if (!m)
|
|
56
85
|
throw new Error('Bad fixtureLocalIso');
|
|
57
86
|
const [_, y, M, d, h, mnt] = m;
|
|
58
|
-
// YYMMDDhhmm is 10 digits; compress to len digits via mod 10^len.
|
|
59
87
|
const compact = Number(`${y.slice(2)}${M}${d}${h}${mnt}`); // e.g. 2510312000
|
|
60
88
|
const n = compact % 10 ** len;
|
|
61
89
|
return pad(n, len);
|
|
@@ -65,9 +93,11 @@ const fixtureBlockNum = (fixtureLocalIso, len = 6) => {
|
|
|
65
93
|
}
|
|
66
94
|
};
|
|
67
95
|
exports.fixtureBlockNum = fixtureBlockNum;
|
|
96
|
+
/**
|
|
97
|
+
* Cryptographically strong random numeric block of the given length.
|
|
98
|
+
*/
|
|
68
99
|
const rndBlockNum = (len = 3) => {
|
|
69
100
|
try {
|
|
70
|
-
// Uniform cryptographic integer in [0, 10^len)
|
|
71
101
|
const max = 10 ** len;
|
|
72
102
|
return pad((0, node_crypto_1.randomInt)(0, max), len);
|
|
73
103
|
}
|
|
@@ -76,31 +106,46 @@ const rndBlockNum = (len = 3) => {
|
|
|
76
106
|
}
|
|
77
107
|
};
|
|
78
108
|
exports.rndBlockNum = rndBlockNum;
|
|
79
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Build a numeric ticket code with blocks ordered to match the picture *in reverse*:
|
|
111
|
+
* Random (3) + Sector/Block/Col (3) + Date/Time (6) + Fixture/Match ID (2) + Luhn(1)
|
|
112
|
+
*
|
|
113
|
+
* Default dashed display groups: 3-3-6-2-1
|
|
114
|
+
*
|
|
115
|
+
* Lengths are chosen to keep the overall size at 15 digits (incl. Luhn),
|
|
116
|
+
* which plays nicely with scanners and manual entry.
|
|
117
|
+
*/
|
|
80
118
|
const buildTicketCodeNumeric = (args) => {
|
|
81
119
|
try {
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const S = (0, exports.seatBlockNum)(args.sectionId, args.row, args.col); // 3
|
|
85
|
-
const
|
|
86
|
-
|
|
120
|
+
// --- individual blocks ---
|
|
121
|
+
const R = (0, exports.rndBlockNum)(args.rndLen ?? 3); // Random (3)
|
|
122
|
+
const S = (0, exports.seatBlockNum)(args.sectionId, args.row, args.col); // Seat (3)
|
|
123
|
+
const D = (0, exports.fixtureBlockNum)(args.fixtureLocalIso, 6); // Date/Time (6)
|
|
124
|
+
// Fixture/Match ID (2): prefer fixtureId; fallback to venue code hash
|
|
125
|
+
const F = args.fixtureId ? (0, exports.fixtureIdBlockNum)(args.fixtureId, 2) : (0, exports.venueBlockNum)(args.venueCode, 2);
|
|
126
|
+
// --- assemble (REVERSED order vs the picture’s left-to-right) ---
|
|
127
|
+
const body = `${R}${S}${D}${F}`; // 3+3+6+2 = 14
|
|
87
128
|
const C = (0, exports.luhn10)(body); // 1 digit
|
|
88
|
-
const numeric = `${body}${C}`; // 15 digits
|
|
129
|
+
const numeric = `${body}${C}`; // total 15 digits
|
|
89
130
|
if (args.dashed === false)
|
|
90
131
|
return numeric;
|
|
91
|
-
//
|
|
92
|
-
return `${
|
|
132
|
+
// Readability grouping for scanners/ushers: 3-3-6-2-1
|
|
133
|
+
return `${R}-${S}-${D}-${F}-${C}`;
|
|
93
134
|
}
|
|
94
135
|
catch {
|
|
95
136
|
return '';
|
|
96
137
|
}
|
|
97
138
|
};
|
|
98
139
|
exports.buildTicketCodeNumeric = buildTicketCodeNumeric;
|
|
140
|
+
/**
|
|
141
|
+
* Validate a code built by buildTicketCodeNumeric (dashes optional).
|
|
142
|
+
* Checks: numeric, correct total length (15), and Luhn checksum.
|
|
143
|
+
*/
|
|
99
144
|
const validateTicketCodeNumeric = (code) => {
|
|
100
145
|
try {
|
|
101
146
|
const clean = code.replace(/-/g, '');
|
|
102
147
|
if (!/^\d{15}$/.test(clean))
|
|
103
|
-
return false; //
|
|
148
|
+
return false; // 3+3+6+2+1
|
|
104
149
|
const body = clean.slice(0, -1);
|
|
105
150
|
const check = clean.slice(-1);
|
|
106
151
|
return (0, exports.luhn10)(body) === check;
|
|
@@ -110,8 +155,12 @@ const validateTicketCodeNumeric = (code) => {
|
|
|
110
155
|
}
|
|
111
156
|
};
|
|
112
157
|
exports.validateTicketCodeNumeric = validateTicketCodeNumeric;
|
|
158
|
+
/**
|
|
159
|
+
* A stable, human-readable internal ID for a seat within a fixture.
|
|
160
|
+
* (Not used in the printed/scanned code — just handy elsewhere.)
|
|
161
|
+
*/
|
|
113
162
|
const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
114
163
|
exports.getTicketId = getTicketId;
|
|
115
|
-
//
|
|
164
|
+
// Compatibility aliases (if other code imports these names already)
|
|
116
165
|
exports.buildTicketCode = exports.buildTicketCodeNumeric;
|
|
117
166
|
exports.validateTicketCode = exports.validateTicketCodeNumeric;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibiliaze/stringman",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"pub": "npm publish --access public",
|
|
10
|
-
"git": "git add .; git commit -m 'changes'; git tag -a v3.14.
|
|
10
|
+
"git": "git add .; git commit -m 'changes'; git tag -a v3.14.1 -m 'v3.14.1'; git push origin v3.14.1; git push",
|
|
11
11
|
"push": "npm run build; npm run git; npm run pub"
|
|
12
12
|
},
|
|
13
13
|
"author": "Ibi Hasanli",
|
package/src/seat.ts
CHANGED
|
@@ -17,4 +17,4 @@ export const getItemId = ({
|
|
|
17
17
|
|
|
18
18
|
export const getFromItemId = (
|
|
19
19
|
itemId: string
|
|
20
|
-
): { fixtureId: string; sectionId: string; seatId: string; categoryId:
|
|
20
|
+
): { fixtureId: string; sectionId: string; seatId: string; categoryId: string } => JSON.parse(itemId);
|
package/src/ticket.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { createHash, randomInt } from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Left-pad a positive integer with zeros to a fixed width.
|
|
5
|
+
*/
|
|
4
6
|
const pad = (n: number, len: number) => n.toString().padStart(len, '0');
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Compute a Luhn mod-10 check digit for a numeric string.
|
|
10
|
+
* @param digits Numeric string (no separators).
|
|
11
|
+
* @returns Single check digit as a string.
|
|
12
|
+
*/
|
|
7
13
|
export const luhn10 = (digits: string): string => {
|
|
8
14
|
let sum = 0;
|
|
9
15
|
let dbl = false;
|
|
@@ -20,11 +26,13 @@ export const luhn10 = (digits: string): string => {
|
|
|
20
26
|
return String(check);
|
|
21
27
|
};
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Compact, stable 2-digit number for a venue or any short code.
|
|
31
|
+
* Uses a small SHA-1 based hash and modulo to fit the requested width.
|
|
32
|
+
*/
|
|
24
33
|
export const venueBlockNum = (venueCode: string, len = 2) => {
|
|
25
34
|
try {
|
|
26
|
-
|
|
27
|
-
const h = createHash('sha1').update(venueCode.toUpperCase()).digest(); // 20 bytes
|
|
35
|
+
const h = createHash('sha1').update(venueCode.toUpperCase()).digest();
|
|
28
36
|
const n = ((h[0] << 8) | h[1]) % 10 ** len;
|
|
29
37
|
return pad(n, len);
|
|
30
38
|
} catch {
|
|
@@ -32,9 +40,25 @@ export const venueBlockNum = (venueCode: string, len = 2) => {
|
|
|
32
40
|
}
|
|
33
41
|
};
|
|
34
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Compact, stable 2-digit number for a Fixture/Match ID string.
|
|
45
|
+
* If you pass your DB fixtureId here, you’ll get a short numeric block.
|
|
46
|
+
*/
|
|
47
|
+
export const fixtureIdBlockNum = (fixtureId: string, len = 2) => {
|
|
48
|
+
try {
|
|
49
|
+
const h = createHash('sha1').update(fixtureId).digest();
|
|
50
|
+
const n = ((h[0] << 8) | h[1]) % 10 ** len;
|
|
51
|
+
return pad(n, len);
|
|
52
|
+
} catch {
|
|
53
|
+
return ''.padStart(len, '0');
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Compact, stable 3-digit number for a specific seat (section + row + col).
|
|
59
|
+
*/
|
|
35
60
|
export const seatBlockNum = (sectionId: string, row: number, col: number, len = 3) => {
|
|
36
61
|
try {
|
|
37
|
-
// Hash -> 16-bit -> modulo 10^len
|
|
38
62
|
const h = createHash('sha1').update(`${sectionId}:${row}:${col}`).digest();
|
|
39
63
|
const n16 = (h[0] << 8) | h[1];
|
|
40
64
|
const n = n16 % 10 ** len;
|
|
@@ -44,13 +68,16 @@ export const seatBlockNum = (sectionId: string, row: number, col: number, len =
|
|
|
44
68
|
}
|
|
45
69
|
};
|
|
46
70
|
|
|
71
|
+
/**
|
|
72
|
+
* 6-digit date/time block derived from local start time.
|
|
73
|
+
* Input format expected: "YYYY-MM-DDThh:mm" (local, no 'Z').
|
|
74
|
+
* Encodes YYMMDDhhmm then reduces modulo 10^len to keep width stable.
|
|
75
|
+
*/
|
|
47
76
|
export const fixtureBlockNum = (fixtureLocalIso: string, len = 6) => {
|
|
48
77
|
try {
|
|
49
|
-
// Expect "YYYY-MM-DDThh:mm" (local, no Z).
|
|
50
78
|
const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
|
|
51
79
|
if (!m) throw new Error('Bad fixtureLocalIso');
|
|
52
80
|
const [_, y, M, d, h, mnt] = m;
|
|
53
|
-
// YYMMDDhhmm is 10 digits; compress to len digits via mod 10^len.
|
|
54
81
|
const compact = Number(`${y.slice(2)}${M}${d}${h}${mnt}`); // e.g. 2510312000
|
|
55
82
|
const n = compact % 10 ** len;
|
|
56
83
|
return pad(n, len);
|
|
@@ -59,9 +86,11 @@ export const fixtureBlockNum = (fixtureLocalIso: string, len = 6) => {
|
|
|
59
86
|
}
|
|
60
87
|
};
|
|
61
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Cryptographically strong random numeric block of the given length.
|
|
91
|
+
*/
|
|
62
92
|
export const rndBlockNum = (len = 3) => {
|
|
63
93
|
try {
|
|
64
|
-
// Uniform cryptographic integer in [0, 10^len)
|
|
65
94
|
const max = 10 ** len;
|
|
66
95
|
return pad(randomInt(0, max), len);
|
|
67
96
|
} catch {
|
|
@@ -69,38 +98,63 @@ export const rndBlockNum = (len = 3) => {
|
|
|
69
98
|
}
|
|
70
99
|
};
|
|
71
100
|
|
|
72
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Build a numeric ticket code with blocks ordered to match the picture *in reverse*:
|
|
103
|
+
* Random (3) + Sector/Block/Col (3) + Date/Time (6) + Fixture/Match ID (2) + Luhn(1)
|
|
104
|
+
*
|
|
105
|
+
* Default dashed display groups: 3-3-6-2-1
|
|
106
|
+
*
|
|
107
|
+
* Lengths are chosen to keep the overall size at 15 digits (incl. Luhn),
|
|
108
|
+
* which plays nicely with scanners and manual entry.
|
|
109
|
+
*/
|
|
73
110
|
export const buildTicketCodeNumeric = (args: {
|
|
74
|
-
|
|
75
|
-
|
|
111
|
+
/** Human/ops venue short code (e.g. "SB"). Used only if fixtureId is not provided. */
|
|
112
|
+
venueCode: string;
|
|
113
|
+
/** Local fixture start time, "YYYY-MM-DDThh:mm" (no 'Z'). */
|
|
114
|
+
fixtureLocalIso: string;
|
|
115
|
+
/** Section identifier as used in your DB. */
|
|
76
116
|
sectionId: string;
|
|
117
|
+
/** Seat row number. */
|
|
77
118
|
row: number;
|
|
119
|
+
/** Seat column/number. */
|
|
78
120
|
col: number;
|
|
79
|
-
|
|
80
|
-
|
|
121
|
+
/** Optional: your DB fixtureId. If present, becomes the 2-digit “Fixture/Match ID” block. */
|
|
122
|
+
fixtureId?: string;
|
|
123
|
+
/** Random block length (default 3). */
|
|
124
|
+
rndLen?: number;
|
|
125
|
+
/** Show dashes for readability (default true). */
|
|
126
|
+
dashed?: boolean;
|
|
81
127
|
}) => {
|
|
82
128
|
try {
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const S = seatBlockNum(args.sectionId, args.row, args.col); // 3
|
|
86
|
-
const
|
|
129
|
+
// --- individual blocks ---
|
|
130
|
+
const R = rndBlockNum(args.rndLen ?? 3); // Random (3)
|
|
131
|
+
const S = seatBlockNum(args.sectionId, args.row, args.col); // Seat (3)
|
|
132
|
+
const D = fixtureBlockNum(args.fixtureLocalIso, 6); // Date/Time (6)
|
|
133
|
+
// Fixture/Match ID (2): prefer fixtureId; fallback to venue code hash
|
|
134
|
+
const F = args.fixtureId ? fixtureIdBlockNum(args.fixtureId, 2) : venueBlockNum(args.venueCode, 2);
|
|
87
135
|
|
|
88
|
-
|
|
136
|
+
// --- assemble (REVERSED order vs the picture’s left-to-right) ---
|
|
137
|
+
const body = `${R}${S}${D}${F}`; // 3+3+6+2 = 14
|
|
89
138
|
const C = luhn10(body); // 1 digit
|
|
90
|
-
const numeric = `${body}${C}`; // 15 digits
|
|
139
|
+
const numeric = `${body}${C}`; // total 15 digits
|
|
91
140
|
|
|
92
141
|
if (args.dashed === false) return numeric;
|
|
93
|
-
|
|
94
|
-
|
|
142
|
+
|
|
143
|
+
// Readability grouping for scanners/ushers: 3-3-6-2-1
|
|
144
|
+
return `${R}-${S}-${D}-${F}-${C}`;
|
|
95
145
|
} catch {
|
|
96
146
|
return '';
|
|
97
147
|
}
|
|
98
148
|
};
|
|
99
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Validate a code built by buildTicketCodeNumeric (dashes optional).
|
|
152
|
+
* Checks: numeric, correct total length (15), and Luhn checksum.
|
|
153
|
+
*/
|
|
100
154
|
export const validateTicketCodeNumeric = (code: string) => {
|
|
101
155
|
try {
|
|
102
156
|
const clean = code.replace(/-/g, '');
|
|
103
|
-
if (!/^\d{15}$/.test(clean)) return false; //
|
|
157
|
+
if (!/^\d{15}$/.test(clean)) return false; // 3+3+6+2+1
|
|
104
158
|
const body = clean.slice(0, -1);
|
|
105
159
|
const check = clean.slice(-1);
|
|
106
160
|
return luhn10(body) === check;
|
|
@@ -109,6 +163,10 @@ export const validateTicketCodeNumeric = (code: string) => {
|
|
|
109
163
|
}
|
|
110
164
|
};
|
|
111
165
|
|
|
166
|
+
/**
|
|
167
|
+
* A stable, human-readable internal ID for a seat within a fixture.
|
|
168
|
+
* (Not used in the printed/scanned code — just handy elsewhere.)
|
|
169
|
+
*/
|
|
112
170
|
export const getTicketId = ({
|
|
113
171
|
fixtureId,
|
|
114
172
|
sectionName,
|
|
@@ -119,6 +177,6 @@ export const getTicketId = ({
|
|
|
119
177
|
seatName: string;
|
|
120
178
|
}) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
121
179
|
|
|
122
|
-
//
|
|
180
|
+
// Compatibility aliases (if other code imports these names already)
|
|
123
181
|
export const buildTicketCode = buildTicketCodeNumeric;
|
|
124
182
|
export const validateTicketCode = validateTicketCodeNumeric;
|