@curless/hotel-storefront 0.30.3 → 0.30.4

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.
@@ -1,6 +1,6 @@
1
1
  import { randomBytes, randomUUID } from 'node:crypto';
2
2
  import { DEFAULT_PAGE, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, guestTooLong, partyNeeds, } from '../backend.js';
3
- import { addDays, arrivalInstant, atHotelTime, nights as nightsBetween, stayProblem, todayAt, } from '../dates.js';
3
+ import { addDays, arrivalInstant, atHotelTime, nights as nightsBetween, stayProblem, stayProblemOf, todayAt, } from '../dates.js';
4
4
  import { byWords, splitByStay } from '../demo-hotels.js';
5
5
  import { FACILITIES, hasFacility } from '../facilities.js';
6
6
  import { endOf, startProblem } from '../hourly.js';
@@ -583,6 +583,7 @@ export const createHuazhuHotels = (opts) => {
583
583
  ok: false,
584
584
  kind: 'too-far',
585
585
  why: `rooms here are on sale at most ${sellDays} days ahead`,
586
+ args: { days: sellDays },
586
587
  },
587
588
  }))
588
589
  .filter((x) => !(x.roomType && x.roomType.status === 0));
@@ -799,6 +800,18 @@ export const createHuazhuHotels = (opts) => {
799
800
  ...(maxChildren > 0 && maxChildren < 99 ? { maxChildren } : {}),
800
801
  };
801
802
  };
803
+ /** A party too big for any room here, said by partyNeeds with its numbers beside it. */
804
+ const partyWhy = (guests, largest) => ({
805
+ why: partyNeeds(guests, largest),
806
+ code: 'party',
807
+ ...(largest > 0 ? { args: { largest, rooms: Math.ceil(guests / largest) } } : {}),
808
+ });
809
+ /** A hotel's "nothing to book", as HotelDetail carries it. */
810
+ const noRoomsOf = (w) => ({
811
+ noRooms: w.why,
812
+ noRoomsCode: w.code,
813
+ ...(w.args ? { noRoomsArgs: w.args } : {}),
814
+ });
802
815
  /** The most a room here sleeps, from rates refused for the party ("sleeps 2"). */
803
816
  const largestOf = (fails) => Math.max(...fails.map((f) => Number(/\d+/.exec(f.why)?.[0] ?? 0)));
804
817
  /**
@@ -811,49 +824,84 @@ export const createHuazhuHotels = (opts) => {
811
824
  */
812
825
  const hourlyNoRoomsWhy = (priced, guests) => {
813
826
  if (priced.length === 0)
814
- return 'This hotel sells no rooms by the hour today.';
827
+ return { why: 'This hotel sells no rooms by the hour today.', code: 'hourly-none' };
815
828
  const fails = priced.map((p) => p.res).filter((r) => !r.ok);
816
829
  if (fails.length > 0 && fails.every((f) => f.kind === 'party'))
817
- return partyNeeds(guests, largestOf(fails));
830
+ return partyWhy(guests, largestOf(fails));
818
831
  if (fails.some((f) => f.kind === 'prepaid'))
819
- return "This hotel's rooms by the hour are paid in advance, and this connector books rates paid at the hotel.";
832
+ return {
833
+ why: "This hotel's rooms by the hour are paid in advance, and this connector books rates paid at the hotel.",
834
+ code: 'prepaid-only',
835
+ };
820
836
  const said = (re) => fails
821
837
  .map((f) => (f.kind === 'closed' ? re.exec(f.why)?.[1] : undefined))
822
838
  .filter((t) => !!t)
823
839
  .sort();
824
840
  const opens = said(/on sale today from (\d\d:\d\d)/)[0];
825
841
  if (opens)
826
- return `Rooms by the hour here go on sale today from ${opens}.`;
842
+ return {
843
+ why: `Rooms by the hour here go on sale today from ${opens}.`,
844
+ code: 'hourly-not-yet',
845
+ args: { opensAt: opens },
846
+ };
827
847
  const last = said(/the last start today was (\d\d:\d\d)/).pop();
828
848
  if (last)
829
- return `No room by the hour is left here today: the last start today was ${last}.`;
830
- return 'No room by the hour is left here today.';
849
+ return {
850
+ why: `No room by the hour is left here today: the last start today was ${last}.`,
851
+ code: 'hourly-over',
852
+ args: { lastStart: last },
853
+ };
854
+ return { why: 'No room by the hour is left here today.', code: 'sold-out' };
831
855
  };
832
856
  /** Why nothing here can be sold for these nights - the most useful reason first. */
833
857
  const noRoomsWhy = (priced, stay, rooms) => {
834
858
  if (priced.length === 0)
835
- return 'This hotel has no rooms on sale through this connector.';
859
+ return {
860
+ why: 'This hotel has no rooms on sale through this connector.',
861
+ code: 'none-on-sale',
862
+ };
836
863
  const fails = priced.map((p) => p.res).filter((r) => !r.ok);
837
864
  const kinds = new Set(fails.map((f) => f.kind));
838
865
  const only = (k) => kinds.size === 1 && kinds.has(k);
839
866
  if (only('party'))
840
- return partyNeeds(stay.guests, largestOf(fails));
867
+ return partyWhy(stay.guests, largestOf(fails));
841
868
  if (only('prepaid'))
842
- return 'Every rate here for those nights is paid in advance, and this connector books rates paid at the hotel.';
869
+ return {
870
+ why: 'Every rate here for those nights is paid in advance, and this connector books rates paid at the hotel.',
871
+ code: 'prepaid-only',
872
+ };
843
873
  if (only('sold-out'))
844
874
  return rooms > 1
845
- ? `No room type here has ${rooms} left for those nights.`
846
- : 'Every room type here is taken for those nights.';
875
+ ? {
876
+ why: `No room type here has ${rooms} left for those nights.`,
877
+ code: 'sold-out',
878
+ args: { rooms },
879
+ }
880
+ : { why: 'Every room type here is taken for those nights.', code: 'sold-out' };
847
881
  if (only('too-many-rooms'))
848
- return `No rate here books ${rooms} rooms at once; ask for fewer.`;
882
+ return {
883
+ why: `No rate here books ${rooms} rooms at once; ask for fewer.`,
884
+ code: 'too-many-rooms',
885
+ args: { rooms },
886
+ };
849
887
  if (only('too-far'))
850
- return `Not on sale yet: ${fails[0]?.why ?? 'rooms here are not sold that far ahead'}.`;
888
+ return {
889
+ why: `Not on sale yet: ${fails[0]?.why ?? 'rooms here are not sold that far ahead'}.`,
890
+ code: 'too-far',
891
+ ...(fails[0]?.args?.days ? { args: { days: fails[0].args.days } } : {}),
892
+ };
851
893
  const rule = fails.find((f) => f.kind === 'stay-rule');
852
894
  if (rule)
853
- return `Not for those nights: ${rule.why}.`;
895
+ return {
896
+ why: `Not for those nights: ${rule.why}.`,
897
+ // Booked too far ahead for this rate is "too far", said with its days.
898
+ ...(rule.args?.days !== undefined && !rule.args.rule
899
+ ? { code: 'too-far', args: { days: rule.args.days } }
900
+ : { code: 'stay-rule', ...(rule.args ? { args: rule.args } : {}) }),
901
+ };
854
902
  if (kinds.has('closed'))
855
- return 'Not on sale for those nights.';
856
- return 'No room here can be booked for those nights.';
903
+ return { why: 'Not on sale for those nights.', code: 'closed' };
904
+ return { why: 'No room here can be booked for those nights.', code: 'unavailable' };
857
905
  };
858
906
  // --------------------------------------------------------------- quote ----
859
907
  const quoteLines = async (trip, fresh = false) => {
@@ -2037,11 +2085,14 @@ export const createHuazhuHotels = (opts) => {
2037
2085
  }
2038
2086
  catch {
2039
2087
  pricedRooms.set(raw.hotelId, []);
2040
- why.set(raw.hotelId, 'Rooms by the hour could not be read from Huazhu just now; open the hotel for them.');
2088
+ why.set(raw.hotelId, {
2089
+ why: 'Rooms by the hour could not be read from Huazhu just now; open the hotel for them.',
2090
+ code: 'unreachable',
2091
+ });
2041
2092
  }
2042
2093
  return;
2043
2094
  }
2044
- const problem = stayProblem(stay, today());
2095
+ const problem = stayProblemOf(stay, today());
2045
2096
  if (problem) {
2046
2097
  pricedRooms.set(raw.hotelId, []);
2047
2098
  why.set(raw.hotelId, problem);
@@ -2055,9 +2106,12 @@ export const createHuazhuHotels = (opts) => {
2055
2106
  }
2056
2107
  catch (err) {
2057
2108
  pricedRooms.set(raw.hotelId, []);
2058
- why.set(raw.hotelId, err instanceof HuazhuRefusal && err.code === 2009
2059
- ? 'Huazhu is answering slowly just now; open the hotel for its rooms.'
2060
- : 'Prices could not be read from Huazhu just now; open the hotel for its rooms.');
2109
+ why.set(raw.hotelId, {
2110
+ why: err instanceof HuazhuRefusal && err.code === 2009
2111
+ ? 'Huazhu is answering slowly just now; open the hotel for its rooms.'
2112
+ : 'Prices could not be read from Huazhu just now; open the hotel for its rooms.',
2113
+ code: 'unreachable',
2114
+ });
2061
2115
  }
2062
2116
  };
2063
2117
  const bestFor = (raw, over) => {
@@ -2157,11 +2211,18 @@ export const createHuazhuHotels = (opts) => {
2157
2211
  if (!stay)
2158
2212
  return m.hotel;
2159
2213
  const b = bestFor(m.raw, f);
2160
- if (!b)
2214
+ if (!b) {
2215
+ const w = why.get(m.raw.hotelId) ?? {
2216
+ why: 'No room here can be booked for those nights.',
2217
+ code: 'unavailable',
2218
+ };
2161
2219
  return {
2162
2220
  ...m.hotel,
2163
- noRoomsForStay: why.get(m.raw.hotelId) ?? 'No room here can be booked for those nights.',
2221
+ noRoomsForStay: w.why,
2222
+ noRoomsForStayCode: w.code,
2223
+ ...(w.args ? { noRoomsForStayArgs: w.args } : {}),
2164
2224
  };
2225
+ }
2165
2226
  return {
2166
2227
  ...m.hotel,
2167
2228
  forStay: {
@@ -2250,9 +2311,9 @@ export const createHuazhuHotels = (opts) => {
2250
2311
  const detail = { hotel: toHotel(raw, view, now()) };
2251
2312
  if (!stay)
2252
2313
  return detail;
2253
- const problem = stayProblem(stay, today());
2314
+ const problem = stayProblemOf(stay, today());
2254
2315
  if (problem)
2255
- return { ...detail, noRooms: problem };
2316
+ return { ...detail, ...noRoomsOf(problem) };
2256
2317
  let priced;
2257
2318
  try {
2258
2319
  priced = await priceStay(raw, stay, 1);
@@ -2261,7 +2322,10 @@ export const createHuazhuHotels = (opts) => {
2261
2322
  return {
2262
2323
  ...detail,
2263
2324
  stay,
2264
- noRooms: `Huazhu could not be asked for this hotel's rooms just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2325
+ ...noRoomsOf({
2326
+ why: `Huazhu could not be asked for this hotel's rooms just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2327
+ code: 'unreachable',
2328
+ }),
2265
2329
  };
2266
2330
  }
2267
2331
  const rooms = priced
@@ -2272,7 +2336,7 @@ export const createHuazhuHotels = (opts) => {
2272
2336
  ...detail,
2273
2337
  stay,
2274
2338
  rooms,
2275
- ...(rooms.length === 0 ? { noRooms: noRoomsWhy(priced, stay, 1) } : {}),
2339
+ ...(rooms.length === 0 ? noRoomsOf(noRoomsWhy(priced, stay, 1)) : {}),
2276
2340
  };
2277
2341
  },
2278
2342
  async hourlyRooms(id, guests) {
@@ -2290,7 +2354,10 @@ export const createHuazhuHotels = (opts) => {
2290
2354
  return {
2291
2355
  hotel,
2292
2356
  stay,
2293
- noRooms: `Huazhu could not be asked for this hotel's rooms by the hour just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2357
+ ...noRoomsOf({
2358
+ why: `Huazhu could not be asked for this hotel's rooms by the hour just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2359
+ code: 'unreachable',
2360
+ }),
2294
2361
  };
2295
2362
  }
2296
2363
  const rooms = priced
@@ -2301,7 +2368,7 @@ export const createHuazhuHotels = (opts) => {
2301
2368
  hotel,
2302
2369
  stay,
2303
2370
  rooms,
2304
- ...(rooms.length === 0 ? { noRooms: hourlyNoRoomsWhy(priced, guests) } : {}),
2371
+ ...(rooms.length === 0 ? noRoomsOf(hourlyNoRoomsWhy(priced, guests)) : {}),
2305
2372
  };
2306
2373
  },
2307
2374
  async datesAround(id, stay, o) {