@ibiliaze/stringman 3.14.1 → 3.16.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 CHANGED
@@ -154,6 +154,7 @@ export declare const ticket: {
154
154
  sectionId: string;
155
155
  row: number;
156
156
  col: number;
157
+ fixtureId?: string;
157
158
  rndLen?: number;
158
159
  dashed?: boolean;
159
160
  }) => string;
package/dist/ticket.d.ts CHANGED
@@ -1,30 +1,89 @@
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;
2
- export declare const venueBlockNum: (venueCode: string, len?: number) => string;
3
- export declare const seatBlockNum: (sectionId: string, row: number, col: number, len?: number) => string;
4
- export declare const fixtureBlockNum: (fixtureLocalIso: string, len?: number) => string;
5
- export declare const rndBlockNum: (len?: number) => string;
7
+ /**
8
+ * Compact, stable numeric block from a short venue code.
9
+ * Uses the first two bytes of a SHA-1 digest modulo 10^len.
10
+ */
11
+ export declare const venueBlockNum: (venueCode: string, len?: 3) => string;
12
+ /**
13
+ * Compact, stable numeric block from a Fixture/Match ID string.
14
+ */
15
+ export declare const fixtureIdBlockNum: (fixtureId: string, len?: 3) => string;
16
+ /**
17
+ * Compact, stable numeric block for a specific seat (section + row + col).
18
+ */
19
+ export declare const seatBlockNum: (sectionId: string, row: number, col: number, len?: 3) => string;
20
+ /**
21
+ * 4-digit date/time block derived from local start time.
22
+ * Input format expected: "YYYY-MM-DDThh:mm" (local, no 'Z').
23
+ * Encodes YYMMDDhhmm then reduces modulo 10^len to keep width stable.
24
+ */
25
+ export declare const fixtureBlockNum: (fixtureLocalIso: string, len?: 4) => string;
26
+ /**
27
+ * Cryptographically strong random numeric block of the given length.
28
+ */
29
+ export declare const rndBlockNum: (len?: 6) => string;
30
+ /**
31
+ * Build a numeric ticket code with blocks ordered to match the picture *in reverse*:
32
+ * Random (6) + Seat (3) + Date/Time (4) + Fixture/Match ID (3) + Luhn(1)
33
+ *
34
+ * Default dashed display groups: 6-3-4-3-1
35
+ *
36
+ * Lengths are chosen to keep the overall size at 17 digits (incl. Luhn),
37
+ * which plays nicely with scanners and manual entry.
38
+ */
6
39
  export declare const buildTicketCodeNumeric: (args: {
40
+ /** Human/ops venue short code (e.g. "SB"). Used only if fixtureId is not provided. */
7
41
  venueCode: string;
42
+ /** Local fixture start time, "YYYY-MM-DDThh:mm" (no 'Z'). */
8
43
  fixtureLocalIso: string;
44
+ /** Section identifier as used in your DB. */
9
45
  sectionId: string;
46
+ /** Seat row number. */
10
47
  row: number;
48
+ /** Seat column/number. */
11
49
  col: number;
50
+ /** Optional: your DB fixtureId. If present, becomes the 3-digit “Fixture/Match ID” block. */
51
+ fixtureId?: string;
52
+ /** Optional override for random block length (defaults to 6). */
12
53
  rndLen?: number;
54
+ /** Show dashes for readability (default true). */
13
55
  dashed?: boolean;
14
56
  }) => string;
57
+ /**
58
+ * Validate a code built by buildTicketCodeNumeric (dashes optional).
59
+ * Checks: numeric, correct total length (17), and Luhn checksum.
60
+ */
15
61
  export declare const validateTicketCodeNumeric: (code: string) => boolean;
62
+ /**
63
+ * A stable, human-readable internal ID for a seat within a fixture.
64
+ * (Not used in the printed/scanned code — just handy elsewhere.)
65
+ */
16
66
  export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
17
67
  fixtureId: string;
18
68
  sectionName: string;
19
69
  seatName: string;
20
70
  }) => string;
21
71
  export declare const buildTicketCode: (args: {
72
+ /** Human/ops venue short code (e.g. "SB"). Used only if fixtureId is not provided. */
22
73
  venueCode: string;
74
+ /** Local fixture start time, "YYYY-MM-DDThh:mm" (no 'Z'). */
23
75
  fixtureLocalIso: string;
76
+ /** Section identifier as used in your DB. */
24
77
  sectionId: string;
78
+ /** Seat row number. */
25
79
  row: number;
80
+ /** Seat column/number. */
26
81
  col: number;
82
+ /** Optional: your DB fixtureId. If present, becomes the 3-digit “Fixture/Match ID” block. */
83
+ fixtureId?: string;
84
+ /** Optional override for random block length (defaults to 6). */
27
85
  rndLen?: number;
86
+ /** Show dashes for readability (default true). */
28
87
  dashed?: boolean;
29
88
  }) => string;
30
89
  export declare const validateTicketCode: (code: string) => boolean;
package/dist/ticket.js CHANGED
@@ -1,10 +1,32 @@
1
1
  "use strict";
2
+ // =====================================================================================
3
+ // Ticket Code Utilities — Format: 6-3-4-3-1 (Random • Seat • Date/Time • Fixture • Luhn)
4
+ // Total length = 17 digits (including Luhn). Dashes optional for readability.
5
+ // =====================================================================================
2
6
  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;
7
+ 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
8
  const node_crypto_1 = require("node:crypto");
5
- // ---------- helpers ----------
9
+ // ---------- config: block lengths (easy to tweak in one place) ----------
10
+ const CODE_LEN = {
11
+ rnd: 6, // Random block
12
+ seat: 3, // Seat identity (section + row + col hashed to 3 digits)
13
+ date: 4, // Fixture local datetime compacted to 4 digits
14
+ fix: 3, // Fixture/Match ID block
15
+ check: 1, // Luhn check digit
16
+ };
17
+ const EXPECTED_TOTAL_LEN = CODE_LEN.rnd + CODE_LEN.seat + CODE_LEN.date + CODE_LEN.fix + CODE_LEN.check; // 17
18
+ // =====================================================================================
19
+ // Helpers
20
+ // =====================================================================================
21
+ /**
22
+ * Left-pad a positive integer with zeros to a fixed width.
23
+ */
6
24
  const pad = (n, len) => n.toString().padStart(len, '0');
7
- // Luhn mod-10 for digits
25
+ /**
26
+ * Compute a Luhn mod-10 check digit for a numeric string.
27
+ * @param digits Numeric string (no separators).
28
+ * @returns Single check digit as a string.
29
+ */
8
30
  const luhn10 = (digits) => {
9
31
  let sum = 0;
10
32
  let dbl = false;
@@ -22,11 +44,13 @@ const luhn10 = (digits) => {
22
44
  return String(check);
23
45
  };
24
46
  exports.luhn10 = luhn10;
25
- // ---------- numeric blocks ----------
26
- const venueBlockNum = (venueCode, len = 2) => {
47
+ /**
48
+ * Compact, stable numeric block from a short venue code.
49
+ * Uses the first two bytes of a SHA-1 digest modulo 10^len.
50
+ */
51
+ const venueBlockNum = (venueCode, len = CODE_LEN.fix) => {
27
52
  try {
28
- // Small, stable hash of the code -> 0..(10^len - 1)
29
- const h = (0, node_crypto_1.createHash)('sha1').update(venueCode.toUpperCase()).digest(); // 20 bytes
53
+ const h = (0, node_crypto_1.createHash)('sha1').update(venueCode.toUpperCase()).digest();
30
54
  const n = ((h[0] << 8) | h[1]) % 10 ** len;
31
55
  return pad(n, len);
32
56
  }
@@ -35,9 +59,25 @@ const venueBlockNum = (venueCode, len = 2) => {
35
59
  }
36
60
  };
37
61
  exports.venueBlockNum = venueBlockNum;
38
- const seatBlockNum = (sectionId, row, col, len = 3) => {
62
+ /**
63
+ * Compact, stable numeric block from a Fixture/Match ID string.
64
+ */
65
+ const fixtureIdBlockNum = (fixtureId, len = CODE_LEN.fix) => {
66
+ try {
67
+ const h = (0, node_crypto_1.createHash)('sha1').update(fixtureId).digest();
68
+ const n = ((h[0] << 8) | h[1]) % 10 ** len;
69
+ return pad(n, len);
70
+ }
71
+ catch {
72
+ return ''.padStart(len, '0');
73
+ }
74
+ };
75
+ exports.fixtureIdBlockNum = fixtureIdBlockNum;
76
+ /**
77
+ * Compact, stable numeric block for a specific seat (section + row + col).
78
+ */
79
+ const seatBlockNum = (sectionId, row, col, len = CODE_LEN.seat) => {
39
80
  try {
40
- // Hash -> 16-bit -> modulo 10^len
41
81
  const h = (0, node_crypto_1.createHash)('sha1').update(`${sectionId}:${row}:${col}`).digest();
42
82
  const n16 = (h[0] << 8) | h[1];
43
83
  const n = n16 % 10 ** len;
@@ -48,16 +88,19 @@ const seatBlockNum = (sectionId, row, col, len = 3) => {
48
88
  }
49
89
  };
50
90
  exports.seatBlockNum = seatBlockNum;
51
- const fixtureBlockNum = (fixtureLocalIso, len = 6) => {
91
+ /**
92
+ * 4-digit date/time block derived from local start time.
93
+ * Input format expected: "YYYY-MM-DDThh:mm" (local, no 'Z').
94
+ * Encodes YYMMDDhhmm then reduces modulo 10^len to keep width stable.
95
+ */
96
+ const fixtureBlockNum = (fixtureLocalIso, len = CODE_LEN.date) => {
52
97
  try {
53
- // Expect "YYYY-MM-DDThh:mm" (local, no Z).
54
98
  const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
55
99
  if (!m)
56
100
  throw new Error('Bad fixtureLocalIso');
57
101
  const [_, y, M, d, h, mnt] = m;
58
- // YYMMDDhhmm is 10 digits; compress to len digits via mod 10^len.
59
102
  const compact = Number(`${y.slice(2)}${M}${d}${h}${mnt}`); // e.g. 2510312000
60
- const n = compact % 10 ** len;
103
+ const n = compact % 10 ** len; // now 4 digits by default
61
104
  return pad(n, len);
62
105
  }
63
106
  catch {
@@ -65,9 +108,11 @@ const fixtureBlockNum = (fixtureLocalIso, len = 6) => {
65
108
  }
66
109
  };
67
110
  exports.fixtureBlockNum = fixtureBlockNum;
68
- const rndBlockNum = (len = 3) => {
111
+ /**
112
+ * Cryptographically strong random numeric block of the given length.
113
+ */
114
+ const rndBlockNum = (len = CODE_LEN.rnd) => {
69
115
  try {
70
- // Uniform cryptographic integer in [0, 10^len)
71
116
  const max = 10 ** len;
72
117
  return pad((0, node_crypto_1.randomInt)(0, max), len);
73
118
  }
@@ -76,31 +121,52 @@ const rndBlockNum = (len = 3) => {
76
121
  }
77
122
  };
78
123
  exports.rndBlockNum = rndBlockNum;
79
- // ---------- builder & validator (numeric-only) ----------
124
+ // =====================================================================================
125
+ /**
126
+ * Build a numeric ticket code with blocks ordered to match the picture *in reverse*:
127
+ * Random (6) + Seat (3) + Date/Time (4) + Fixture/Match ID (3) + Luhn(1)
128
+ *
129
+ * Default dashed display groups: 6-3-4-3-1
130
+ *
131
+ * Lengths are chosen to keep the overall size at 17 digits (incl. Luhn),
132
+ * which plays nicely with scanners and manual entry.
133
+ */
80
134
  const buildTicketCodeNumeric = (args) => {
81
135
  try {
82
- const VV = (0, exports.venueBlockNum)(args.venueCode, 2); // 2 digits
83
- const E = (0, exports.fixtureBlockNum)(args.fixtureLocalIso, 6); // 6 digits
84
- const S = (0, exports.seatBlockNum)(args.sectionId, args.row, args.col); // 3 digits
85
- const R = (0, exports.rndBlockNum)(args.rndLen ?? 3); // 3 digits
86
- const body = `${VV}${E}${S}${R}`; // 14 digits
136
+ // --- individual blocks (new lengths) ---
137
+ const R = (0, exports.rndBlockNum)(6); // Random (6)
138
+ const S = (0, exports.seatBlockNum)(args.sectionId, args.row, args.col, CODE_LEN.seat); // Seat (3)
139
+ const D = (0, exports.fixtureBlockNum)(args.fixtureLocalIso, CODE_LEN.date); // Date/Time (4)
140
+ // Fixture/Match ID (3): prefer fixtureId; fallback to venue code hash (also 3)
141
+ const F = args.fixtureId
142
+ ? (0, exports.fixtureIdBlockNum)(args.fixtureId, CODE_LEN.fix)
143
+ : (0, exports.venueBlockNum)(args.venueCode, CODE_LEN.fix);
144
+ // --- assemble (REVERSED order vs the picture’s left-to-right) ---
145
+ const body = `${R}${S}${D}${F}`; // 6+3+4+3 = 16
87
146
  const C = (0, exports.luhn10)(body); // 1 digit
88
- const numeric = `${body}${C}`; // 15 digits total
147
+ const numeric = `${body}${C}`; // total 17 digits
89
148
  if (args.dashed === false)
90
149
  return numeric;
91
- // Grouping 2-6-3-3-1 for readability
92
- return `${VV}-${E}-${S}-${R}-${C}`;
150
+ // Readability grouping for scanners/ushers: 6-3-4-3-1
151
+ return `${R}-${S}-${D}-${F}-${C}`;
93
152
  }
94
153
  catch {
95
154
  return '';
96
155
  }
97
156
  };
98
157
  exports.buildTicketCodeNumeric = buildTicketCodeNumeric;
158
+ // =====================================================================================
159
+ /**
160
+ * Validate a code built by buildTicketCodeNumeric (dashes optional).
161
+ * Checks: numeric, correct total length (17), and Luhn checksum.
162
+ */
99
163
  const validateTicketCodeNumeric = (code) => {
100
164
  try {
101
165
  const clean = code.replace(/-/g, '');
102
- if (!/^\d{15}$/.test(clean))
103
- return false; // 2+6+3+3+1
166
+ if (!/^\d+$/.test(clean))
167
+ return false;
168
+ if (clean.length !== EXPECTED_TOTAL_LEN)
169
+ return false; // 17
104
170
  const body = clean.slice(0, -1);
105
171
  const check = clean.slice(-1);
106
172
  return (0, exports.luhn10)(body) === check;
@@ -110,8 +176,13 @@ const validateTicketCodeNumeric = (code) => {
110
176
  }
111
177
  };
112
178
  exports.validateTicketCodeNumeric = validateTicketCodeNumeric;
179
+ // =====================================================================================
180
+ /**
181
+ * A stable, human-readable internal ID for a seat within a fixture.
182
+ * (Not used in the printed/scanned code — just handy elsewhere.)
183
+ */
113
184
  const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
114
185
  exports.getTicketId = getTicketId;
115
- // ---------- compatibility shim (if you want same names as before) ----------
186
+ // Compatibility aliases (if other code imports these names already)
116
187
  exports.buildTicketCode = exports.buildTicketCodeNumeric;
117
188
  exports.validateTicketCode = exports.validateTicketCodeNumeric;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@ibiliaze/stringman",
3
- "version": "3.14.1",
3
+ "version": "3.16.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.1 -m 'v3.14.1'; git push origin v3.14.1; git push",
10
+ "git": "git add .; git commit -m 'changes'; git tag -a 3.16.0 -m '3.16.0'; git push origin 3.16.0; git push",
11
11
  "push": "npm run build; npm run git; npm run pub"
12
12
  },
13
13
  "author": "Ibi Hasanli",
package/src/ticket.ts CHANGED
@@ -1,9 +1,35 @@
1
+ // =====================================================================================
2
+ // Ticket Code Utilities — Format: 6-3-4-3-1 (Random • Seat • Date/Time • Fixture • Luhn)
3
+ // Total length = 17 digits (including Luhn). Dashes optional for readability.
4
+ // =====================================================================================
5
+
1
6
  import { createHash, randomInt } from 'node:crypto';
2
7
 
3
- // ---------- helpers ----------
8
+ // ---------- config: block lengths (easy to tweak in one place) ----------
9
+ const CODE_LEN = {
10
+ rnd: 6, // Random block
11
+ seat: 3, // Seat identity (section + row + col hashed to 3 digits)
12
+ date: 4, // Fixture local datetime compacted to 4 digits
13
+ fix: 3, // Fixture/Match ID block
14
+ check: 1, // Luhn check digit
15
+ } as const;
16
+
17
+ const EXPECTED_TOTAL_LEN = CODE_LEN.rnd + CODE_LEN.seat + CODE_LEN.date + CODE_LEN.fix + CODE_LEN.check; // 17
18
+
19
+ // =====================================================================================
20
+ // Helpers
21
+ // =====================================================================================
22
+
23
+ /**
24
+ * Left-pad a positive integer with zeros to a fixed width.
25
+ */
4
26
  const pad = (n: number, len: number) => n.toString().padStart(len, '0');
5
27
 
6
- // Luhn mod-10 for digits
28
+ /**
29
+ * Compute a Luhn mod-10 check digit for a numeric string.
30
+ * @param digits Numeric string (no separators).
31
+ * @returns Single check digit as a string.
32
+ */
7
33
  export const luhn10 = (digits: string): string => {
8
34
  let sum = 0;
9
35
  let dbl = false;
@@ -20,11 +46,13 @@ export const luhn10 = (digits: string): string => {
20
46
  return String(check);
21
47
  };
22
48
 
23
- // ---------- numeric blocks ----------
24
- export const venueBlockNum = (venueCode: string, len = 2) => {
49
+ /**
50
+ * Compact, stable numeric block from a short venue code.
51
+ * Uses the first two bytes of a SHA-1 digest modulo 10^len.
52
+ */
53
+ export const venueBlockNum = (venueCode: string, len = CODE_LEN.fix) => {
25
54
  try {
26
- // Small, stable hash of the code -> 0..(10^len - 1)
27
- const h = createHash('sha1').update(venueCode.toUpperCase()).digest(); // 20 bytes
55
+ const h = createHash('sha1').update(venueCode.toUpperCase()).digest();
28
56
  const n = ((h[0] << 8) | h[1]) % 10 ** len;
29
57
  return pad(n, len);
30
58
  } catch {
@@ -32,9 +60,24 @@ export const venueBlockNum = (venueCode: string, len = 2) => {
32
60
  }
33
61
  };
34
62
 
35
- export const seatBlockNum = (sectionId: string, row: number, col: number, len = 3) => {
63
+ /**
64
+ * Compact, stable numeric block from a Fixture/Match ID string.
65
+ */
66
+ export const fixtureIdBlockNum = (fixtureId: string, len = CODE_LEN.fix) => {
67
+ try {
68
+ const h = createHash('sha1').update(fixtureId).digest();
69
+ const n = ((h[0] << 8) | h[1]) % 10 ** len;
70
+ return pad(n, len);
71
+ } catch {
72
+ return ''.padStart(len, '0');
73
+ }
74
+ };
75
+
76
+ /**
77
+ * Compact, stable numeric block for a specific seat (section + row + col).
78
+ */
79
+ export const seatBlockNum = (sectionId: string, row: number, col: number, len = CODE_LEN.seat) => {
36
80
  try {
37
- // Hash -> 16-bit -> modulo 10^len
38
81
  const h = createHash('sha1').update(`${sectionId}:${row}:${col}`).digest();
39
82
  const n16 = (h[0] << 8) | h[1];
40
83
  const n = n16 % 10 ** len;
@@ -44,24 +87,29 @@ export const seatBlockNum = (sectionId: string, row: number, col: number, len =
44
87
  }
45
88
  };
46
89
 
47
- export const fixtureBlockNum = (fixtureLocalIso: string, len = 6) => {
90
+ /**
91
+ * 4-digit date/time block derived from local start time.
92
+ * Input format expected: "YYYY-MM-DDThh:mm" (local, no 'Z').
93
+ * Encodes YYMMDDhhmm then reduces modulo 10^len to keep width stable.
94
+ */
95
+ export const fixtureBlockNum = (fixtureLocalIso: string, len = CODE_LEN.date) => {
48
96
  try {
49
- // Expect "YYYY-MM-DDThh:mm" (local, no Z).
50
97
  const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
51
98
  if (!m) throw new Error('Bad fixtureLocalIso');
52
99
  const [_, y, M, d, h, mnt] = m;
53
- // YYMMDDhhmm is 10 digits; compress to len digits via mod 10^len.
54
100
  const compact = Number(`${y.slice(2)}${M}${d}${h}${mnt}`); // e.g. 2510312000
55
- const n = compact % 10 ** len;
101
+ const n = compact % 10 ** len; // now 4 digits by default
56
102
  return pad(n, len);
57
103
  } catch {
58
104
  return ''.padStart(len, '0');
59
105
  }
60
106
  };
61
107
 
62
- export const rndBlockNum = (len = 3) => {
108
+ /**
109
+ * Cryptographically strong random numeric block of the given length.
110
+ */
111
+ export const rndBlockNum = (len = CODE_LEN.rnd) => {
63
112
  try {
64
- // Uniform cryptographic integer in [0, 10^len)
65
113
  const max = 10 ** len;
66
114
  return pad(randomInt(0, max), len);
67
115
  } catch {
@@ -69,38 +117,68 @@ export const rndBlockNum = (len = 3) => {
69
117
  }
70
118
  };
71
119
 
72
- // ---------- builder & validator (numeric-only) ----------
120
+ // =====================================================================================
121
+ /**
122
+ * Build a numeric ticket code with blocks ordered to match the picture *in reverse*:
123
+ * Random (6) + Seat (3) + Date/Time (4) + Fixture/Match ID (3) + Luhn(1)
124
+ *
125
+ * Default dashed display groups: 6-3-4-3-1
126
+ *
127
+ * Lengths are chosen to keep the overall size at 17 digits (incl. Luhn),
128
+ * which plays nicely with scanners and manual entry.
129
+ */
73
130
  export const buildTicketCodeNumeric = (args: {
74
- venueCode: string; // e.g. "SB"
75
- fixtureLocalIso: string; // "2025-10-31T20:00" in venue TZ
131
+ /** Human/ops venue short code (e.g. "SB"). Used only if fixtureId is not provided. */
132
+ venueCode: string;
133
+ /** Local fixture start time, "YYYY-MM-DDThh:mm" (no 'Z'). */
134
+ fixtureLocalIso: string;
135
+ /** Section identifier as used in your DB. */
76
136
  sectionId: string;
137
+ /** Seat row number. */
77
138
  row: number;
139
+ /** Seat column/number. */
78
140
  col: number;
79
- rndLen?: number; // default 3 (use 4 if you want more entropy)
80
- dashed?: boolean; // default true (format with dashes)
141
+ /** Optional: your DB fixtureId. If present, becomes the 3-digit “Fixture/Match ID” block. */
142
+ fixtureId?: string;
143
+ /** Optional override for random block length (defaults to 6). */
144
+ rndLen?: number;
145
+ /** Show dashes for readability (default true). */
146
+ dashed?: boolean;
81
147
  }) => {
82
148
  try {
83
- const VV = venueBlockNum(args.venueCode, 2); // 2 digits
84
- const E = fixtureBlockNum(args.fixtureLocalIso, 6); // 6 digits
85
- const S = seatBlockNum(args.sectionId, args.row, args.col); // 3 digits
86
- const R = rndBlockNum(args.rndLen ?? 3); // 3 digits
149
+ // --- individual blocks (new lengths) ---
150
+ const R = rndBlockNum(6); // Random (6)
151
+ const S = seatBlockNum(args.sectionId, args.row, args.col, CODE_LEN.seat); // Seat (3)
152
+ const D = fixtureBlockNum(args.fixtureLocalIso, CODE_LEN.date); // Date/Time (4)
153
+ // Fixture/Match ID (3): prefer fixtureId; fallback to venue code hash (also 3)
154
+ const F = args.fixtureId
155
+ ? fixtureIdBlockNum(args.fixtureId, CODE_LEN.fix)
156
+ : venueBlockNum(args.venueCode, CODE_LEN.fix);
87
157
 
88
- const body = `${VV}${E}${S}${R}`; // 14 digits
158
+ // --- assemble (REVERSED order vs the picture’s left-to-right) ---
159
+ const body = `${R}${S}${D}${F}`; // 6+3+4+3 = 16
89
160
  const C = luhn10(body); // 1 digit
90
- const numeric = `${body}${C}`; // 15 digits total
161
+ const numeric = `${body}${C}`; // total 17 digits
91
162
 
92
163
  if (args.dashed === false) return numeric;
93
- // Grouping 2-6-3-3-1 for readability
94
- return `${VV}-${E}-${S}-${R}-${C}`;
164
+
165
+ // Readability grouping for scanners/ushers: 6-3-4-3-1
166
+ return `${R}-${S}-${D}-${F}-${C}`;
95
167
  } catch {
96
168
  return '';
97
169
  }
98
170
  };
99
171
 
172
+ // =====================================================================================
173
+ /**
174
+ * Validate a code built by buildTicketCodeNumeric (dashes optional).
175
+ * Checks: numeric, correct total length (17), and Luhn checksum.
176
+ */
100
177
  export const validateTicketCodeNumeric = (code: string) => {
101
178
  try {
102
179
  const clean = code.replace(/-/g, '');
103
- if (!/^\d{15}$/.test(clean)) return false; // 2+6+3+3+1
180
+ if (!/^\d+$/.test(clean)) return false;
181
+ if (clean.length !== EXPECTED_TOTAL_LEN) return false; // 17
104
182
  const body = clean.slice(0, -1);
105
183
  const check = clean.slice(-1);
106
184
  return luhn10(body) === check;
@@ -109,6 +187,11 @@ export const validateTicketCodeNumeric = (code: string) => {
109
187
  }
110
188
  };
111
189
 
190
+ // =====================================================================================
191
+ /**
192
+ * A stable, human-readable internal ID for a seat within a fixture.
193
+ * (Not used in the printed/scanned code — just handy elsewhere.)
194
+ */
112
195
  export const getTicketId = ({
113
196
  fixtureId,
114
197
  sectionName,
@@ -119,6 +202,6 @@ export const getTicketId = ({
119
202
  seatName: string;
120
203
  }) => `${sectionName}-${seatName}-${fixtureId}`;
121
204
 
122
- // ---------- compatibility shim (if you want same names as before) ----------
205
+ // Compatibility aliases (if other code imports these names already)
123
206
  export const buildTicketCode = buildTicketCodeNumeric;
124
207
  export const validateTicketCode = validateTicketCodeNumeric;