sabre-client 0.5.0 → 0.6.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/testing.js CHANGED
@@ -126,6 +126,304 @@ function lineFields(value) {
126
126
  };
127
127
  }
128
128
 
129
+ // src/queue/host-date.ts
130
+ var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
131
+ var DAY_MS = 864e5;
132
+ var HALF_YEAR_DAYS = 184;
133
+ function resolveHostDate(text2, referenceDate, options = {}) {
134
+ const reference = referenceDay(referenceDate);
135
+ const direction = options.direction ?? "past";
136
+ if (direction !== "past" && direction !== "nearest") {
137
+ throw new TypeError("sabre-client: direction debe ser 'past' o 'nearest'");
138
+ }
139
+ if (typeof text2 !== "string") return void 0;
140
+ const parsed = parseHostDate(text2.trim().toUpperCase());
141
+ if (parsed === void 0) return void 0;
142
+ const year = parsed.year ?? resolveYear(parsed.month, parsed.day, reference, direction);
143
+ if (year === void 0 || !isValidDate(year, parsed.month, parsed.day)) return void 0;
144
+ const date = `${pad(year, 4)}-${pad(parsed.month, 2)}-${pad(parsed.day, 2)}`;
145
+ return parsed.time === void 0 ? date : `${date}T${parsed.time}`;
146
+ }
147
+ function parseHostDate(text2) {
148
+ let match = /^(\d{1,2})([A-Z]{3})(\d{2})?$/.exec(text2);
149
+ if (match !== null) {
150
+ const month2 = MONTHS.indexOf(match[2]) + 1;
151
+ if (month2 === 0) return void 0;
152
+ return {
153
+ ...match[3] !== void 0 && { year: 2e3 + Number(match[3]) },
154
+ month: month2,
155
+ day: Number(match[1])
156
+ };
157
+ }
158
+ match = /^(?:(\d{4})-)?(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(text2);
159
+ if (match === null) return void 0;
160
+ const [, year, month, day, hours, minutes] = match;
161
+ if (hours !== void 0 && (Number(hours) > 23 || Number(minutes) > 59)) return void 0;
162
+ return {
163
+ ...year !== void 0 && { year: Number(year) },
164
+ month: Number(month),
165
+ day: Number(day),
166
+ ...hours !== void 0 && { time: `${hours}:${minutes}` }
167
+ };
168
+ }
169
+ function resolveYear(month, day, reference, direction) {
170
+ const year = new Date(reference).getUTCFullYear();
171
+ if (direction === "past") {
172
+ for (let candidate = year; candidate >= year - 8; candidate--) {
173
+ if (isValidDate(candidate, month, day) && dayNumber(candidate, month, day) <= reference) return candidate;
174
+ }
175
+ return void 0;
176
+ }
177
+ let best;
178
+ for (const candidate of [year + 1, year, year - 1]) {
179
+ if (!isValidDate(candidate, month, day)) continue;
180
+ const distance = Math.abs(dayNumber(candidate, month, day) - reference);
181
+ if (best === void 0 || distance < best.distance) best = { year: candidate, distance };
182
+ }
183
+ return best !== void 0 && best.distance <= HALF_YEAR_DAYS * DAY_MS ? best.year : void 0;
184
+ }
185
+ function referenceDay(referenceDate) {
186
+ if (referenceDate instanceof Date) {
187
+ const time = referenceDate.getTime();
188
+ if (Number.isNaN(time)) throw new TypeError("sabre-client: referenceDate es un Date inv\xE1lido");
189
+ return time - (time % DAY_MS + DAY_MS) % DAY_MS;
190
+ }
191
+ if (typeof referenceDate === "string") {
192
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:T.*)?$/.exec(referenceDate.trim());
193
+ if (match !== null) {
194
+ const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
195
+ if (isValidDate(year, month, day)) return dayNumber(year, month, day);
196
+ }
197
+ }
198
+ throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
199
+ }
200
+ function dayNumber(year, month, day) {
201
+ const date = /* @__PURE__ */ new Date(0);
202
+ date.setUTCFullYear(year, month - 1, day);
203
+ return date.getTime();
204
+ }
205
+ function isValidDate(year, month, day) {
206
+ if (month < 1 || month > 12 || day < 1) return false;
207
+ const date = /* @__PURE__ */ new Date(0);
208
+ date.setUTCFullYear(year, month - 1, day);
209
+ return date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
210
+ }
211
+ function pad(value, length) {
212
+ return String(value).padStart(length, "0");
213
+ }
214
+
215
+ // src/queue/queue-paragraph.ts
216
+ var MESSAGE = /^\s*(\d{3})\s{2,}(\S.*?)\s*$/;
217
+ var PASSENGER_LINE = /^\s*\d+\.\d+[A-Z]/;
218
+ var PASSENGER_SPLIT = /\s+(?=\d+\.\d+[A-Z])/;
219
+ var PASSENGER = /^(\d+\.\d+)([^/*]+?)\s*(?:\/([^*]*?))?\s*(?:\*(.*?))?\s*$/;
220
+ var AIR_SEGMENT = /^\s*(\d{1,2})\s+([A-Z0-9]{2})\s*(\d{1,4})([A-Z])\s+(\d{2}[A-Z]{3})\s+[A-Z0-9]\s+([A-Z]{3})([A-Z]{3})\s+([A-Z]{2})(\d{1,3})(?=\s|$)(.*)$/;
221
+ var SEGMENT_TIMES = /^\s+(\d{3,4}[APNM]?)\s+(\d{3,4}[APNM]?)(?=\s|$)/;
222
+ var OTHER_SEGMENT = /^\s*(\d{1,2})\s+([A-Z]{3})\s+([A-Z0-9]{2})\s+(\d{2}[A-Z]{3})\s+[A-Z0-9]\s+([A-Z]{2})(\d{1,3})(?=\s|$)/;
223
+ var NO_ITINERARY = /^\s*NO ITIN\s*$/;
224
+ var TICKETING_HEADER = /^\s*TKT\/TIME LIMIT\s*$/;
225
+ var TICKETING_ENTRY = /^\s*(\d+)\.(.*?)\s*$/;
226
+ var TICKETED = /^T-(\d{2}[A-Z]{3})?(?:-([A-Z0-9]{3,4})\*([A-Z0-9]+))?(?:\/.*)?$/;
227
+ var ARRANGEMENT = /^(TA[WCMTX])([A-Z0-9]{3,4})?(\d{2}[A-Z]{3})?\d{0,3}\/.*$/;
228
+ var TICKET = /^(TE|TK)\s+(\d{13})\b/;
229
+ var RECEIVED = /^\s*RECEIVED FROM -\s?(.*?)\s*$/;
230
+ var SIGNATURE = /^\s*([A-Z0-9]{3,4})\.(\S+)\s+(\d{2})(\d{2})\/(\d{2}[A-Z]{3}\d{2})\s+([A-Z0-9]{6})\s+H\s*$/;
231
+ var Phase = { Messages: 0, Passengers: 1, Segments: 2, Body: 3 };
232
+ function parseQueueParagraph(lines, options) {
233
+ if (!Array.isArray(lines)) throw new TypeError("sabre-client: lines debe ser un array de strings");
234
+ if (typeof options !== "object" || options === null) {
235
+ throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
236
+ }
237
+ const { referenceDate } = options;
238
+ referenceDay(referenceDate);
239
+ const result = { messages: [], passengers: [], segments: [], unparsed: [] };
240
+ const signatureIndex = lastSignatureIndex(lines);
241
+ let phase = Phase.Messages;
242
+ let inTicketing = false;
243
+ lines.forEach((value, index) => {
244
+ const line = typeof value === "string" ? value : "";
245
+ if (line.trim() === "") return;
246
+ if (index === signatureIndex) {
247
+ result.receivedFrom = { ...result.receivedFrom, ...parseSignature(line, referenceDate) };
248
+ return;
249
+ }
250
+ if (inTicketing) {
251
+ const entry = TICKETING_ENTRY.exec(line);
252
+ if (entry !== null) {
253
+ const parsed = parseTicketingEntry(Number(entry[1]), entry[2], referenceDate);
254
+ if (parsed === void 0) result.unparsed.push(line);
255
+ else result.ticketing.entries.push(parsed);
256
+ return;
257
+ }
258
+ inTicketing = false;
259
+ }
260
+ if (TICKETING_HEADER.test(line)) {
261
+ phase = Phase.Body;
262
+ inTicketing = true;
263
+ result.ticketing ??= { entries: [], issued: false };
264
+ return;
265
+ }
266
+ const received = RECEIVED.exec(line);
267
+ if (received !== null) {
268
+ phase = Phase.Body;
269
+ result.receivedFrom = { ...result.receivedFrom, ...received[1] !== "" && { name: received[1] } };
270
+ return;
271
+ }
272
+ if (phase === Phase.Messages) {
273
+ const message = MESSAGE.exec(line);
274
+ if (message !== null) {
275
+ result.messages.push({ instructionCode: Number(message[1]), text: message[2] });
276
+ return;
277
+ }
278
+ }
279
+ if (phase <= Phase.Passengers && PASSENGER_LINE.test(line)) {
280
+ const passengers = parsePassengers(line);
281
+ if (passengers !== void 0) {
282
+ phase = Phase.Passengers;
283
+ result.passengers.push(...passengers);
284
+ return;
285
+ }
286
+ }
287
+ if (phase <= Phase.Segments) {
288
+ if (NO_ITINERARY.test(line)) {
289
+ phase = Phase.Segments;
290
+ return;
291
+ }
292
+ const segment = parseSegment(line, referenceDate);
293
+ if (segment !== void 0) {
294
+ phase = Phase.Segments;
295
+ result.segments.push(segment);
296
+ return;
297
+ }
298
+ }
299
+ result.unparsed.push(line);
300
+ });
301
+ if (result.ticketing !== void 0) {
302
+ result.ticketing.issued = result.ticketing.entries.some((entry) => entry.ticketNumber !== void 0);
303
+ }
304
+ return result;
305
+ }
306
+ function parsePassengers(line) {
307
+ const passengers = [];
308
+ for (const part of line.trim().split(PASSENGER_SPLIT)) {
309
+ const match = PASSENGER.exec(part);
310
+ if (match === null) return void 0;
311
+ const [, nameNumber, surname, givenName, nameRemark] = match;
312
+ passengers.push({
313
+ nameNumber,
314
+ surname: surname.trim(),
315
+ ...givenName !== void 0 && givenName !== "" && { givenName },
316
+ ...nameRemark !== void 0 && nameRemark !== "" && { nameRemark },
317
+ text: part.slice(nameNumber.length).trim()
318
+ });
319
+ }
320
+ return passengers;
321
+ }
322
+ function parseSegment(line, referenceDate) {
323
+ const air = AIR_SEGMENT.exec(line);
324
+ if (air !== null) {
325
+ const [, number2, airlineCode, flightNumber, bookingClass, date2, from, to, status2, quantity2, rest] = air;
326
+ const times = SEGMENT_TIMES.exec(rest);
327
+ const departureTime = times === null ? void 0 : hostTime(times[1]);
328
+ const arrivalTime = times === null ? void 0 : hostTime(times[2]);
329
+ return {
330
+ kind: "air",
331
+ number: Number(number2),
332
+ ...withDate(date2, referenceDate),
333
+ status: status2,
334
+ quantity: Number(quantity2),
335
+ airlineCode,
336
+ flightNumber: Number(flightNumber),
337
+ bookingClass,
338
+ fromAirportCode: from,
339
+ toAirportCode: to,
340
+ ...departureTime !== void 0 && { departureTime },
341
+ ...arrivalTime !== void 0 && { arrivalTime },
342
+ text: line.trim()
343
+ };
344
+ }
345
+ const other = OTHER_SEGMENT.exec(line);
346
+ if (other === null) return void 0;
347
+ const [, number, segmentType, vendorCode, date, status, quantity] = other;
348
+ return {
349
+ kind: "other",
350
+ number: Number(number),
351
+ ...withDate(date, referenceDate),
352
+ status,
353
+ quantity: Number(quantity),
354
+ segmentType,
355
+ vendorCode,
356
+ text: line.trim()
357
+ };
358
+ }
359
+ function parseTicketingEntry(number, text2, referenceDate) {
360
+ const ticketed = TICKETED.exec(text2);
361
+ if (ticketed !== null) {
362
+ const [, date, pseudoCityCode, agentSine] = ticketed;
363
+ return {
364
+ number,
365
+ code: "T-",
366
+ ...date !== void 0 && withDate(date, referenceDate),
367
+ ...pseudoCityCode !== void 0 && { pseudoCityCode },
368
+ ...agentSine !== void 0 && { agentSine },
369
+ text: text2
370
+ };
371
+ }
372
+ const arrangement = ARRANGEMENT.exec(text2);
373
+ if (arrangement !== null) {
374
+ const [, code, pseudoCityCode, date] = arrangement;
375
+ return {
376
+ number,
377
+ code,
378
+ ...date !== void 0 && withDate(date, referenceDate),
379
+ ...pseudoCityCode !== void 0 && { pseudoCityCode },
380
+ text: text2
381
+ };
382
+ }
383
+ const ticket = TICKET.exec(text2);
384
+ if (ticket !== null) return { number, code: ticket[1], ticketNumber: ticket[2], text: text2 };
385
+ return void 0;
386
+ }
387
+ function lastSignatureIndex(lines) {
388
+ for (let index = lines.length - 1; index >= 0; index--) {
389
+ const line = lines[index];
390
+ if (typeof line === "string" && SIGNATURE.test(line)) return index;
391
+ }
392
+ return -1;
393
+ }
394
+ function parseSignature(line, referenceDate) {
395
+ const [, pseudoCityCode, sign, hours, minutes, date, locator] = SIGNATURE.exec(line);
396
+ const agentSine = /\*([A-Z0-9]+)$/.exec(sign)?.[1];
397
+ const day = Number(hours) <= 23 && Number(minutes) <= 59 ? resolveHostDate(date, referenceDate) : void 0;
398
+ return {
399
+ pseudoCityCode,
400
+ ...agentSine !== void 0 && { agentSine },
401
+ ...day !== void 0 && { receivedAt: `${day}T${hours}:${minutes}` },
402
+ locator
403
+ };
404
+ }
405
+ function withDate(text2, referenceDate) {
406
+ const date = resolveHostDate(text2, referenceDate, { direction: "nearest" });
407
+ return date === void 0 ? {} : { date };
408
+ }
409
+ function hostTime(text2) {
410
+ const match = /^(\d{1,2})(\d{2})([APNM])?$/.exec(text2);
411
+ if (match === null) return void 0;
412
+ let hours = Number(match[1]);
413
+ const minutes = Number(match[2]);
414
+ const suffix = match[3];
415
+ if (minutes > 59) return void 0;
416
+ if (suffix === void 0) {
417
+ if (hours > 23) return void 0;
418
+ } else {
419
+ if (hours < 1 || hours > 12) return void 0;
420
+ if (suffix === "A" || suffix === "M") hours %= 12;
421
+ else if (suffix === "P") hours = hours % 12 + 12;
422
+ else if (hours !== 12) return void 0;
423
+ }
424
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
425
+ }
426
+
129
427
  // src/queue/queue-place.ts
130
428
  var SIGNATURE_LINE = /\s\d{4}\/\d{2}[A-Z]{3}\d{2}\s+([A-Z0-9]{6})\s+H\s*$/;
131
429
  function signatureLocator(lines) {
@@ -142,6 +440,15 @@ var STL_NAMESPACE = "http://services.sabre.com/STL/v01";
142
440
  var SIGNATURE_DATE = "1200/01JAN26";
143
441
  var LOCATOR = /^[A-Z0-9]{6}$/;
144
442
  var NAVIGATION = { I: "ignore", QR: "remove", QXI: "exit" };
443
+ var QUEUE_FAKE_SELECTION_FUNCTIONS = ["QD", "AK", "AL", "PI", "AC"];
444
+ var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
445
+ var MONTHS2 = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
446
+ function placedOn(placedAt, criteria) {
447
+ const date = /^(\d{1,2})([A-Z]{3})$/.exec(criteria.toUpperCase());
448
+ const placed = /(\d{2})-(\d{2})T/.exec(placedAt ?? "");
449
+ if (date === null || placed === null) return false;
450
+ return Number(placed[1]) === MONTHS2.indexOf(date[2]) + 1 && Number(placed[2]) === Number(date[1]);
451
+ }
145
452
  function fail(message) {
146
453
  throw new TypeError(`sabre-client/testing: ${message}`);
147
454
  }
@@ -188,9 +495,9 @@ function refOfKey(key) {
188
495
  if (key.startsWith(NAME_PREFIX)) return { name: key.slice(NAME_PREFIX.length) };
189
496
  return /^\d+$/.test(key) ? Number(key) : key;
190
497
  }
191
- function keyAttribute(key, pad = false) {
498
+ function keyAttribute(key, pad2 = false) {
192
499
  if (key.startsWith(NAME_PREFIX)) return `Name="${escape(key.slice(NAME_PREFIX.length))}"`;
193
- return `Number="${escape(pad && /^\d+$/.test(key) ? key.padStart(4, "0") : key)}"`;
500
+ return `Number="${escape(pad2 && /^\d+$/.test(key) ? key.padStart(4, "0") : key)}"`;
194
501
  }
195
502
  function validateKey(key) {
196
503
  if (/^\d+$/.test(key)) {
@@ -438,7 +745,43 @@ function createQueueFake(options = {}) {
438
745
  const placeRS = (inner) => `<QueuePlaceRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.4">${inner}</QueuePlaceRS>`;
439
746
  const accessError = (message) => accessRS(results("NotProcessed", hostResult("Error", message)));
440
747
  const placeError = (message) => placeRS(results("NotProcessed", hostResult("Error", message)));
748
+ const matchesFilter = (entry, fn, criteria) => {
749
+ if (entry.message !== void 0) return false;
750
+ const wanted = criteria.trim().toUpperCase();
751
+ const paragraph = () => parseQueueParagraph(pnrs.get(entry.locator)?.paragraph ?? [], { referenceDate: now() });
752
+ switch (fn.toUpperCase()) {
753
+ case "QD":
754
+ return placedOn(entry.placedAt, wanted);
755
+ case "AK":
756
+ return `${entry.placedBy?.pseudoCityCode ?? ""}${entry.placedBy?.agentSine ?? ""}`.toUpperCase() === wanted;
757
+ case "AL":
758
+ return paragraph().segments.some((segment) => segment.kind === "air" && segment.airlineCode === wanted);
759
+ case "AC":
760
+ return paragraph().segments.some((segment) => segment.status.startsWith(wanted));
761
+ case "PI": {
762
+ const code = Number(wanted);
763
+ if (entry.instructionCode !== void 0) return entry.instructionCode === code;
764
+ return paragraph().messages.some((message) => message.instructionCode === code);
765
+ }
766
+ default:
767
+ return void 0;
768
+ }
769
+ };
770
+ const selectionOf = (rq, allowed) => {
771
+ const filters = nodes(rq.Selection).map((selection) => ({ fn: (attr(selection, "Function") ?? "").toUpperCase(), criteria: attr(selection, "Criteria") ?? "" }));
772
+ if (filters.length === 0) return {};
773
+ const unsupported = filters.find((filter) => !allowed.includes(filter.fn));
774
+ if (unsupported !== void 0) return { error: `sabre-client/testing: selection ${unsupported.fn} no est\xE1 simulado (simula ${allowed.join(", ")})` };
775
+ return { matches: (entry) => filters.every((filter) => matchesFilter(entry, filter.fn, filter.criteria) === true) };
776
+ };
777
+ const advance = (session) => {
778
+ const inside = session.inside;
779
+ if (inside?.matches === void 0) return;
780
+ const entries = queue(inside.key);
781
+ while (inside.index < entries.length && !inside.matches(entries[inside.index])) inside.index += 1;
782
+ };
441
783
  const step = (session) => {
784
+ advance(session);
442
785
  const inside = session.inside;
443
786
  const entry = inside === void 0 ? void 0 : queue(inside.key)[inside.index];
444
787
  if (entry === void 0) {
@@ -452,19 +795,51 @@ function createQueueFake(options = {}) {
452
795
  return accessRS(`${results("Complete", success)}${lineXml(entry)}<Paragraph>${textLines2(pnr.paragraph)}</Paragraph>`);
453
796
  };
454
797
  const waiting = (session, key, entries) => entries.length - (session.inside?.key === key && entries[session.inside.index] !== void 0 ? 1 : 0);
798
+ const countRS = (inner) => `<QueueCountRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.2.1">${inner}</QueueCountRS>`;
455
799
  const count = (session, rq) => {
456
- const pcc = attr(node2(node2(rq.QueueInfo)?.QueueIdentifier), "PseudoCityCode") ?? "";
800
+ const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
801
+ const pcc = attr(identifier, "PseudoCityCode") ?? "";
802
+ const countError = (message) => countRS(results("NotProcessed", hostResult("Error", message)));
803
+ const range = node2(identifier?.Range);
804
+ const requested = requestedKey(identifier);
805
+ const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
806
+ if (matches !== void 0 || error !== void 0) {
807
+ if (range !== void 0) return countError("KEYS NOT ALLOWED");
808
+ if (requested === void 0) return countError("QUEUE SELECTED NOT VALID OR CLOSED");
809
+ if (error !== void 0) return countError(error);
810
+ }
811
+ const info = (identifiers2) => `${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers2.join("")}</QueueInfo>`;
812
+ const totalsXml = (messages2, pnrs2) => `<Totals Count="${messages2}" Type="MESSAGES"/><Totals Count="0" Type="SPECIALS"/><Totals Count="${pnrs2}" Type="PNRS"/>`;
813
+ const waitingIn = (key) => waiting(session, key, queues.get(key) ?? []);
814
+ if (range !== void 0) {
815
+ const start = Number(attr(range, "Start"));
816
+ const end = Number(attr(range, "End"));
817
+ const identifiers2 = [];
818
+ let pnrsTotal2 = 0;
819
+ for (let number = start; number <= end; number++) {
820
+ const total = waitingIn(String(number));
821
+ identifiers2.push(`<QueueIdentifier Count="${total}" Number="${number}"/>`);
822
+ pnrsTotal2 += total;
823
+ }
824
+ return countRS(`${info(identifiers2)}${totalsXml(0, pnrsTotal2)}`);
825
+ }
826
+ if (requested !== void 0) {
827
+ if (matches === void 0) return countRS(info([`<QueueIdentifier Count="${waitingIn(requested)}" ${keyAttribute(requested)}/>`]));
828
+ const onScreen = session.inside?.key === requested ? queue(requested)[session.inside.index] : void 0;
829
+ const matching = (queues.get(requested) ?? []).filter((entry) => entry !== onScreen && matches(entry)).length;
830
+ return countRS(`${info([`<QueueIdentifier Count="${matching}" ${keyAttribute(requested)}/>`])}<Totals Count="${waitingIn(requested)}"/>`);
831
+ }
457
832
  let pnrsTotal = 0;
458
833
  let messages = 0;
459
834
  const identifiers = [];
460
- for (const [key, entries] of queues) {
461
- const total = waiting(session, key, entries);
835
+ for (const [queueKey2, entries] of queues) {
836
+ const total = waiting(session, queueKey2, entries);
462
837
  if (total <= 0) continue;
463
- identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(key)}/>`);
464
- if (ALPHA_KEY.test(key)) messages += total;
838
+ identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(queueKey2)}/>`);
839
+ if (ALPHA_KEY.test(queueKey2)) messages += total;
465
840
  else pnrsTotal += total;
466
841
  }
467
- return `<QueueCountRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.2.1">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo><Totals Count="${messages}" Type="MESSAGES"/><Totals Count="0" Type="SPECIALS"/><Totals Count="${pnrsTotal}" Type="PNRS"/></QueueCountRS>`;
842
+ return countRS(`${info(identifiers)}${totalsXml(messages, pnrsTotal)}`);
468
843
  };
469
844
  const access = (session, rq) => {
470
845
  const navigation = attr(rq.Navigation, "Action");
@@ -474,15 +849,20 @@ function createQueueFake(options = {}) {
474
849
  if (key === void 0) return accessError("sabre-client/testing: QueueIdentifier sin Number ni Name");
475
850
  const isList = node2(identifier?.List) !== void 0;
476
851
  calls.push({ command: isList ? "list" : "access", queue: refOfKey(key) });
477
- if (nodes(rq.Selection).length > 0) return accessError("sabre-client/testing: selection no est\xE1 simulado");
852
+ if (isList && nodes(rq.Selection).some((selection) => !LIST_SELECTION_FUNCTIONS.includes((attr(selection, "Function") ?? "").toUpperCase()))) {
853
+ return accessError("VALID SELECTION CODE FOR QUEUE LIST ARE QD OR AK");
854
+ }
855
+ const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
856
+ if (error !== void 0) return accessError(error);
478
857
  const entries = queue(key);
479
858
  if (entries.length === 0) return accessError(QUEUE_EMPTY);
480
859
  if (isList) {
481
860
  const passenger = attr(identifier?.List, "PrimaryPassenger") === "true";
482
- const lines = entries.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
861
+ const listed = matches === void 0 ? entries : entries.filter(matches);
862
+ const lines = listed.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
483
863
  return accessRS(`${results("Complete", success)}${lines}`);
484
864
  }
485
- session.inside = { key, index: 0 };
865
+ session.inside = { key, index: 0, ...matches === void 0 ? {} : { matches } };
486
866
  return step(session);
487
867
  };
488
868
  const navigate = (session, rq, action) => {
@@ -532,10 +912,12 @@ function createQueueFake(options = {}) {
532
912
  const add = (locator, target) => {
533
913
  const entries = queue(target.key);
534
914
  if (entries.some((entry) => entry.locator === locator)) return;
915
+ const code = Number(target.instructionCode);
535
916
  entries.push({
536
917
  locator,
537
918
  placedAt: longDate(at),
538
- placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc }
919
+ placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc },
920
+ ...Number.isInteger(code) ? { instructionCode: code } : {}
539
921
  });
540
922
  };
541
923
  if (moving) {
@@ -543,6 +925,7 @@ function createQueueFake(options = {}) {
543
925
  if (current === void 0) return placeError("sabre-client/testing: no hay PNR en pantalla");
544
926
  queue(inside.key).splice(inside.index, 1);
545
927
  for (const target of targets) add(current.locator, target);
928
+ advance(session);
546
929
  const next = queue(inside.key)[inside.index];
547
930
  if (next === void 0) {
548
931
  session.inside = void 0;
@@ -565,22 +948,37 @@ function createQueueFake(options = {}) {
565
948
  };
566
949
  const analyze = (session, rq) => {
567
950
  calls.push({ command: "activity" });
568
- const pcc = attr(node2(node2(rq.QueueInfo)?.QueueIdentifier), "PseudoCityCode") ?? "";
569
- const totals = { NumMessages: 0 };
570
- for (const [, element2] of ACTIVITY_COUNTERS) totals[element2] = 0;
571
- const identifiers = [];
572
- for (const key of /* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])) {
951
+ const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
952
+ const pcc = attr(identifier, "PseudoCityCode") ?? "";
953
+ const countersOf = (key) => {
573
954
  const seeded = activity.get(key) ?? {};
574
955
  const counters = { NumMessages: waiting(session, key, queues.get(key) ?? []) };
575
956
  for (const [name, element2] of ACTIVITY_COUNTERS) counters[element2] = seeded[name] ?? 0;
576
- if (Object.values(counters).every((value) => value === 0)) continue;
577
- for (const [element2, value] of Object.entries(counters)) totals[element2] = (totals[element2] ?? 0) + value;
578
- identifiers.push(
579
- `<QueueIdentifier ${keyAttribute(key)}>${Object.entries(counters).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("")}</QueueIdentifier>`
580
- );
957
+ return counters;
958
+ };
959
+ const countersXml = (counters) => Object.entries(counters).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("");
960
+ let keys;
961
+ let withTotals = true;
962
+ const range = node2(identifier?.Range);
963
+ const requested = requestedKey(identifier);
964
+ if (range !== void 0) {
965
+ keys = [];
966
+ for (let number = Number(attr(range, "Start")); number <= Number(attr(range, "End")); number++) keys.push(String(number));
967
+ } else if (requested !== void 0) {
968
+ keys = [requested];
969
+ withTotals = false;
970
+ } else {
971
+ keys = [.../* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])].filter((key) => Object.values(countersOf(key)).some((value) => value !== 0));
581
972
  }
582
- const totalsXml = Object.entries(totals).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("");
583
- return `<QueueAnalysisRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.0">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo><Totals>${totalsXml}</Totals></QueueAnalysisRS>`;
973
+ const totals = { NumMessages: 0 };
974
+ for (const [, element2] of ACTIVITY_COUNTERS) totals[element2] = 0;
975
+ const identifiers = keys.map((key) => {
976
+ const counters = countersOf(key);
977
+ for (const [element2, value] of Object.entries(counters)) totals[element2] = (totals[element2] ?? 0) + value;
978
+ return `<QueueIdentifier ${keyAttribute(key)}>${countersXml(counters)}</QueueIdentifier>`;
979
+ });
980
+ const totalsXml = withTotals ? `<Totals>${countersXml(totals)}</Totals>` : "";
981
+ return `<QueueAnalysisRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.0">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo>${totalsXml}</QueueAnalysisRS>`;
584
982
  };
585
983
  const history = (session, rq) => {
586
984
  const locator = attr(rq.UniqueID, "ID") ?? "";
@@ -3283,6 +3681,7 @@ var queueFixtures = Object.freeze({
3283
3681
  placeWithoutInstructionCodeInside: fixture(queue_production_place_sin_pic_adentro_default)
3284
3682
  });
3285
3683
  export {
3684
+ QUEUE_FAKE_SELECTION_FUNCTIONS,
3286
3685
  createAnonymizer,
3287
3686
  createQueueFake,
3288
3687
  isSafeHostLine,