@curless/hotel-storefront 0.55.6 → 0.55.8
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/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/demo-hotels.d.ts +6 -0
- package/dist/demo-hotels.d.ts.map +1 -1
- package/dist/demo-hotels.js +3 -2
- package/dist/demo-hotels.js.map +1 -1
- package/dist/otaless/backend.d.ts.map +1 -1
- package/dist/otaless/backend.js +6 -2
- package/dist/otaless/backend.js.map +1 -1
- package/dist/otaless/fake.d.ts +6 -3
- package/dist/otaless/fake.d.ts.map +1 -1
- package/dist/otaless/fake.js +19 -9
- package/dist/otaless/fake.js.map +1 -1
- package/dist/otaless/places.d.ts.map +1 -1
- package/dist/otaless/places.js +16 -4
- package/dist/otaless/places.js.map +1 -1
- package/dist/otaless/recorded-1105.d.ts +15 -0
- package/dist/otaless/recorded-1105.d.ts.map +1 -0
- package/dist/otaless/recorded-1105.js +1328 -0
- package/dist/otaless/recorded-1105.js.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -2
- package/dist/server.js.map +1 -1
- package/dist/supplier.d.ts.map +1 -1
- package/dist/supplier.js +1 -0
- package/dist/supplier.js.map +1 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +6 -4
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts +31 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +165 -38
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -589,7 +589,7 @@ const allTools = (n, v) => {
|
|
|
589
589
|
type: 'integer',
|
|
590
590
|
minimum: 0,
|
|
591
591
|
maximum: MAX_EXTRA_QUANTITY,
|
|
592
|
-
description: `For an add-on: how many, 1 to ${MAX_EXTRA_QUANTITY}. Default 1; 0 removes it`,
|
|
592
|
+
description: `For an add-on: how many, 1 to ${MAX_EXTRA_QUANTITY}. Default 1 - or, for an add-on priced per person, everyone in the rooms the trip holds at that hotel (guests per room x rooms; the guests asked for when there is no room yet); 0 removes it`,
|
|
593
593
|
},
|
|
594
594
|
},
|
|
595
595
|
required: ['hotel'],
|
|
@@ -1252,6 +1252,8 @@ const HOTEL_PAGE = {
|
|
|
1252
1252
|
roomFilters: ARR,
|
|
1253
1253
|
/** The filters the call asked for, as the same keys - the card opens with these selected. */
|
|
1254
1254
|
activeFilters: STRS,
|
|
1255
|
+
/** Only where the client draws cards: the hotels, or the rooms, are on the card - `ON_CARD`. */
|
|
1256
|
+
cardShows: STR,
|
|
1255
1257
|
/**
|
|
1256
1258
|
* Room types with no room to book for these nights (none left, or fewer
|
|
1257
1259
|
* than the rooms asked for): `{ id, name, names?, sleeps, ..., left? }`, no
|
|
@@ -1611,6 +1613,24 @@ const asStay = (args) => {
|
|
|
1611
1613
|
const guests = typeof args.guests === 'number' ? Math.floor(args.guests) : 2;
|
|
1612
1614
|
return { checkIn, checkOut, guests };
|
|
1613
1615
|
};
|
|
1616
|
+
/** The add-ons priced by the person: "per person", "per person per night". */
|
|
1617
|
+
const PRICED_BY_PERSON = new Set(['person', 'person-night']);
|
|
1618
|
+
/**
|
|
1619
|
+
* ⚠️ AN ADD-ON PRICED PER PERSON STARTS AT EVERYONE (0.55.7, robin 11-05: Shangri-La's
|
|
1620
|
+
* Bund night cruise, "per person", for two guests was added for ONE). Asked for with no
|
|
1621
|
+
* `quantity`, it is the party of the rooms the trip holds at that hotel - the guests in
|
|
1622
|
+
* each room times the rooms - else the guests the call names, else one. The guest can
|
|
1623
|
+
* change it as before; an add-on priced per stay, night or item stays at 1.
|
|
1624
|
+
*/
|
|
1625
|
+
const partyOnTrip = (deps, key, hotelId, args) => {
|
|
1626
|
+
const room = deps.trips.read(key).find((l) => l.kind === 'room' && l.hotelId === hotelId);
|
|
1627
|
+
const people = room?.kind === 'room'
|
|
1628
|
+
? room.stay.guests * (room.rooms ?? 1)
|
|
1629
|
+
: typeof args.guests === 'number'
|
|
1630
|
+
? Math.floor(args.guests) * (typeof args.rooms === 'number' ? Math.floor(args.rooms) : 1)
|
|
1631
|
+
: 1;
|
|
1632
|
+
return Math.min(Math.max(1, people), MAX_EXTRA_QUANTITY);
|
|
1633
|
+
};
|
|
1614
1634
|
/**
|
|
1615
1635
|
* What every payload carries, so the card can draw a header and say whose data
|
|
1616
1636
|
* this is.
|
|
@@ -1633,6 +1653,11 @@ const memberBadge = (deps, key) => {
|
|
|
1633
1653
|
member: {
|
|
1634
1654
|
programme: m.programme,
|
|
1635
1655
|
tier: { name: m.tier.name, discount: m.tier.discount },
|
|
1656
|
+
// The member's own name, for the booking form's name (robin, 2026-11-05:
|
|
1657
|
+
// 「名字可以自动填」) - a card drawn after the sign-in, in another card or
|
|
1658
|
+
// from the chat, had only the tier to go by.
|
|
1659
|
+
...(m.name ? { name: m.name } : {}),
|
|
1660
|
+
...(m.names ? { names: m.names } : {}),
|
|
1636
1661
|
...(m.preferences?.length ? { preferences: m.preferences } : {}),
|
|
1637
1662
|
// As they stand now - spent or handed back since signing in - so
|
|
1638
1663
|
// the member page the badge opens is never a booking behind.
|
|
@@ -1873,9 +1898,15 @@ const paidLine = (deps, b, fee) => {
|
|
|
1873
1898
|
// ⚠️ NOT "nothing paid": the card page may have been paid a moment ago
|
|
1874
1899
|
// and not read yet (audit, P0-D) - what is said is what is to be done.
|
|
1875
1900
|
return unionpay
|
|
1876
|
-
? `**Not booked yet.** ${amount} is to be confirmed on UnionPay's page by ${p.payByLocal ?? p.payBy} hotel time${p.url ? `: ${p.url}` : ''}.
|
|
1901
|
+
? `**Not booked yet.** ${amount} is to be confirmed on UnionPay's page by ${p.payByLocal ?? p.payBy} hotel time${p.url ? `: ${p.url}` : ''}. ${h.notReal
|
|
1902
|
+
? `Once UnionPay issues its one-time card, the booking is made here - it is not sent to ${h.name}`
|
|
1903
|
+
: `Once UnionPay issues its one-time card, the stay is booked with ${h.name}, who charges that card`}; not confirmed by then, nothing is booked or charged.`
|
|
1877
1904
|
: p.url
|
|
1878
|
-
? `**Not booked yet.** ${amount} is to be paid on the card page by ${p.payByLocal ?? p.payBy} hotel time: ${p.url}.
|
|
1905
|
+
? `**Not booked yet.** ${amount} is to be paid on the card page by ${p.payByLocal ?? p.payBy} hotel time: ${p.url}. ${
|
|
1906
|
+
// ⚠️ NOT "BOOKED WITH" A GROUP IT IS NEVER SENT TO (0.55.7, 总线程 11-05).
|
|
1907
|
+
h.notReal
|
|
1908
|
+
? `Once it is paid the booking is made here - it is not sent to ${h.name} - and ${deps.tools.listBookings} shows it`
|
|
1909
|
+
: `Once it is paid the stay is booked with ${h.name}, and ${deps.tools.listBookings} shows it`}; unpaid by then, the page closes and nothing is booked or charged.`
|
|
1879
1910
|
: // ⚠️ NO LINK IS NOT "undefined" (2026-10-21 re-audit, T2-6): a card
|
|
1880
1911
|
// page that was closed and could not be opened again leaves a stay
|
|
1881
1912
|
// waiting with nowhere to pay it. Say so, and that it lapses.
|
|
@@ -1883,6 +1914,10 @@ const paidLine = (deps, b, fee) => {
|
|
|
1883
1914
|
case 'issued':
|
|
1884
1915
|
return `Confirmed on UnionPay: ${h.name} is being asked to book it now, and ${deps.tools.listBookings} shows when it has. If ${h.name} does not take it, the one-time card is not charged.`;
|
|
1885
1916
|
case 'paid':
|
|
1917
|
+
// Paid on a page that moved no money, for a booking never sent (0.55.7): not
|
|
1918
|
+
// "booked with" the group - the sentence after it says it was not sent.
|
|
1919
|
+
if (h.notReal)
|
|
1920
|
+
return `${amount} paid ${unionpay ? 'on UnionPay' : 'on the card page'}: the booking is made here.`;
|
|
1886
1921
|
if (unionpay)
|
|
1887
1922
|
return b.status === 'confirmed'
|
|
1888
1923
|
? `${amount} paid with UnionPay, and booked with ${h.name}, who charges the one-time card UnionPay issued: nothing more is paid at the hotel.`
|
|
@@ -1913,23 +1948,26 @@ const confirming = (deps, b) => holderOf(deps, b.supplier).caps.booking === 'pay
|
|
|
1913
1948
|
(b.status === 'requested' || b.status === 'unconfirmed');
|
|
1914
1949
|
/** A booking for the card - with no code while it is being confirmed. */
|
|
1915
1950
|
const madeForCard = (deps, b) => {
|
|
1916
|
-
const card =
|
|
1951
|
+
const card = listedForCard(deps, b);
|
|
1917
1952
|
if (!confirming(deps, b))
|
|
1918
1953
|
return card;
|
|
1919
1954
|
const { confirmationCode: _unconfirmed, ...rest } = card;
|
|
1920
1955
|
return { ...rest, pending: true };
|
|
1921
1956
|
};
|
|
1922
|
-
const bookedLine = (deps, b) => [heldLine(deps, b), couponPointsLine(b)].filter(Boolean).join(' ');
|
|
1957
|
+
const bookedLine = (deps, b) => [heldLine(deps, b), couponPointsLine(b, holderOf(deps, b.supplier))].filter(Boolean).join(' ');
|
|
1923
1958
|
/**
|
|
1924
1959
|
* The coupon a booking used and the points it should earn - on the booking
|
|
1925
1960
|
* itself, so the confirmation says what the review said (§4, §5).
|
|
1926
1961
|
*/
|
|
1927
|
-
const couponPointsLine = (b) => [
|
|
1962
|
+
const couponPointsLine = (b, h) => [
|
|
1928
1963
|
b.quote.coupon
|
|
1929
1964
|
? `Coupon ${couponName(b.quote.coupon)} used: −${formatMoney(b.quote.coupon.off)}, and ${formatMoney(b.quote.total)} is the booking's total after it.`
|
|
1930
1965
|
: '',
|
|
1931
1966
|
b.quote.member
|
|
1932
|
-
?
|
|
1967
|
+
? h?.notReal
|
|
1968
|
+
? // The card's one sentence (`earnsNote`, 0.55.7): nothing sent, nothing earned.
|
|
1969
|
+
`A stay at this rate earns about ${b.quote.member.points.toLocaleString('en-GB')} ${b.quote.member.programme} points; this booking was not sent to ${h.name}, so none are added to the balance.`
|
|
1970
|
+
: `About ${b.quote.member.points.toLocaleString('en-GB')} ${b.quote.member.programme} points after the stay - not added to the balance here.`
|
|
1933
1971
|
: '',
|
|
1934
1972
|
]
|
|
1935
1973
|
.filter(Boolean)
|
|
@@ -1943,7 +1981,7 @@ const heldLine = (deps, b) => {
|
|
|
1943
1981
|
if (h.caps.booking === 'pay-at-hotel') {
|
|
1944
1982
|
// ⚠️ THE FACT, IN NO ENVIRONMENT'S WORD (robin, 2026-11-03).
|
|
1945
1983
|
if (h.notReal)
|
|
1946
|
-
return `${total} all in. **This booking was not sent to ${h.name}** - no room is held for anyone and nothing is paid, now or at any hotel
|
|
1984
|
+
return `${total} all in. **This booking was not sent to ${h.name}** - no room is held for anyone and nothing is paid, now or at any hotel. ${notSentSaid(h.name)}`;
|
|
1947
1985
|
if (h.test)
|
|
1948
1986
|
return `${total} all in. **This booking holds no room for anyone** - nothing is paid, now or at any hotel, and nobody needs to go there.`;
|
|
1949
1987
|
return b.status === 'requested'
|
|
@@ -1962,7 +2000,7 @@ const heldLine = (deps, b) => {
|
|
|
1962
2000
|
// nothing for a real stay; said as how long it lasts, and that it is only the
|
|
1963
2001
|
// demonstration.
|
|
1964
2002
|
if (h.demo)
|
|
1965
|
-
return `${total} all in. **${demoBookingWords(h)}** Nothing has been charged and no card is needed. ${demoHoldWords(b, deps.trips.now())}`;
|
|
2003
|
+
return `${total} all in. **${demoBookingWords(h)}** Nothing has been charged and no card is needed. ${demoHoldWords(b, deps.trips.now())} ${notSentSaid(h.name)}`;
|
|
1966
2004
|
return `${total} all in. **Nothing has been charged** - the rooms are held until ${lapses}, and the guest presents a card at check-in.`;
|
|
1967
2005
|
};
|
|
1968
2006
|
/**
|
|
@@ -1985,6 +2023,18 @@ const demoHoldWords = (b, now) => {
|
|
|
1985
2023
|
};
|
|
1986
2024
|
/** What a booking that is not sent to the group is, in one sentence - see `demoBookingNote`. */
|
|
1987
2025
|
const demoBookingWords = (h) => `This booking was not sent to ${h.name}, and ${h.name} is not holding any room.`;
|
|
2026
|
+
/**
|
|
2027
|
+
* ⚠️ THE FACT, AND NO WORD FOR AN ENVIRONMENT (0.55.8; robin 2026-11-03 「不用特意写不是
|
|
2028
|
+
* 真的」, 总线程 11-05). Told "a booking made here is not sent to Atour", the model on
|
|
2029
|
+
* claude.ai said 「这个亚朵连接器是演示环境」 - a word nobody gave it. Said after each
|
|
2030
|
+
* such fact, in the service instructions and in the answers about a booking, only
|
|
2031
|
+
* where nothing is sent to the group (`notReal`); a group's own test system, which a
|
|
2032
|
+
* booking does reach, is not told this.
|
|
2033
|
+
*/
|
|
2034
|
+
export const notSentSaid = (name) =>
|
|
2035
|
+
// ⚠️ WITHOUT THE WORDS IT FORBIDS: a sentence naming them would be the label in the
|
|
2036
|
+
// model's text (env-one-word.test.ts holds every answer and instruction to none).
|
|
2037
|
+
`Say only that fact - not sent to ${name}, no room held, nothing charged - and nothing about what kind of connector or environment this is: do not call it, ${name} or its hotels a trial, a mock-up or not real.`;
|
|
1988
2038
|
/**
|
|
1989
2039
|
* Said on every answer about a demonstration booking - made, changed or
|
|
1990
2040
|
* cancelled - and on nothing else: a hold of the demo's that a guest took for
|
|
@@ -1992,7 +2042,7 @@ const demoBookingWords = (h) => `This booking was not sent to ${h.name}, and ${h
|
|
|
1992
2042
|
*/
|
|
1993
2043
|
const demoBookingNote = (deps, supplier) => {
|
|
1994
2044
|
const h = holderOf(deps, supplier);
|
|
1995
|
-
return h.demo && h.caps.booking === 'hold' ? demoBookingWords(h) : '';
|
|
2045
|
+
return h.demo && h.caps.booking === 'hold' ? `${demoBookingWords(h)} ${notSentSaid(h.name)}` : '';
|
|
1996
2046
|
};
|
|
1997
2047
|
const common = (deps, key) => {
|
|
1998
2048
|
const caps = deps.backend.capabilities ?? FULL_CAPABILITIES;
|
|
@@ -2245,14 +2295,24 @@ const couponTerms = (c, brand) => [
|
|
|
2245
2295
|
*/
|
|
2246
2296
|
const couponBackWords = (c, fee,
|
|
2247
2297
|
/** Said after the cancellation, not before it. */
|
|
2248
|
-
done = false
|
|
2298
|
+
done = false,
|
|
2299
|
+
/**
|
|
2300
|
+
* The booking was never sent to its group: nothing is charged, and the fee is the
|
|
2301
|
+
* terms' - "this cancellation was charged a fee" beside "nothing is charged" said
|
|
2302
|
+
* money was taken (0.55.7, 总线程 11-05; the card says it the same way).
|
|
2303
|
+
*/
|
|
2304
|
+
notSent = false) => c.returned
|
|
2249
2305
|
? done
|
|
2250
2306
|
? `The coupon ${couponName(c)} is back with the member, usable until ${c.validUntil}: the cancellation was free.`
|
|
2251
2307
|
: `The coupon ${couponName(c)} it used comes back to the member on cancelling, because the cancellation is free and the coupon is usable until ${c.validUntil}.`
|
|
2252
2308
|
: c.notReturnedBecause === 'fee'
|
|
2253
|
-
?
|
|
2254
|
-
?
|
|
2255
|
-
|
|
2309
|
+
? notSent
|
|
2310
|
+
? done
|
|
2311
|
+
? `The coupon ${couponName(c)} is NOT given back: under the terms this cancellation costs a fee${fee ? ` (${fee})` : ''}, and a cancellation that costs a fee counts the coupon as used.`
|
|
2312
|
+
: `The coupon ${couponName(c)} it used does NOT come back: under the terms cancelling costs a fee${fee ? ` (${fee})` : ''}, and a cancellation that costs a fee counts the coupon as used.`
|
|
2313
|
+
: done
|
|
2314
|
+
? `The coupon ${couponName(c)} is NOT given back: this cancellation was charged a fee${fee ? ` (${fee})` : ''}, and a cancellation that is charged counts the coupon as used.`
|
|
2315
|
+
: `The coupon ${couponName(c)} it used does NOT come back: cancelling costs a fee${fee ? ` (${fee})` : ''}, and a cancellation that is charged counts the coupon as used.`
|
|
2256
2316
|
: done
|
|
2257
2317
|
? `The coupon ${couponName(c)} ran out on ${c.validUntil}, so it is not given back.`
|
|
2258
2318
|
: `The coupon ${couponName(c)} it used ran out on ${c.validUntil}, so cancelling does not give it back.`;
|
|
@@ -2798,15 +2858,33 @@ const heldRoomsWords = (b) => b.quote.lines
|
|
|
2798
2858
|
? ', past its free cancellation - cancelling has a fee'
|
|
2799
2859
|
: ''}`)
|
|
2800
2860
|
.join('; ');
|
|
2801
|
-
|
|
2861
|
+
/**
|
|
2862
|
+
* A booking's status said for the model where the booking was never sent to its
|
|
2863
|
+
* group (0.55.7, 总线程 11-05): "requested" - or "confirmed", paid on a page that moved
|
|
2864
|
+
* no money - promised an answer from a group that never heard of it. The card says
|
|
2865
|
+
* the same (`shownStatus`); `undefined` where the status says what it is.
|
|
2866
|
+
*/
|
|
2867
|
+
const notSentStatus = (deps, b) => {
|
|
2868
|
+
const h = holderOf(deps, b.supplier);
|
|
2869
|
+
return h.notReal && ['requested', 'unconfirmed', 'confirmed'].includes(b.status)
|
|
2870
|
+
? `not sent to ${h.name} - nobody confirms it, and no room is held`
|
|
2871
|
+
: undefined;
|
|
2872
|
+
};
|
|
2873
|
+
/** A booking for the card and the model's structure, with `notSentStatus` where there is one. */
|
|
2874
|
+
const listedForCard = (deps, b) => {
|
|
2875
|
+
const said = notSentStatus(deps, b);
|
|
2876
|
+
return { ...bookingForCard(b), ...(said ? { statusSaid: said } : {}) };
|
|
2877
|
+
};
|
|
2878
|
+
const bookingLine = (deps, b, noCards) => {
|
|
2802
2879
|
const kinds = b.requests?.kinds ?? [];
|
|
2803
2880
|
const held = noCards ? heldRoomsWords(b) : '';
|
|
2881
|
+
const notSent = holderOf(deps, b.supplier).notReal;
|
|
2804
2882
|
return [
|
|
2805
|
-
`${b.confirmationCode} · ${withZh(b.hotelName, b.hotelNames, noCards)} · ${stayWords(b.stay, b.hourly)} · ${
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2883
|
+
`${b.confirmationCode} · ${withZh(b.hotelName, b.hotelNames, noCards)} · ${stayWords(b.stay, b.hourly)} · ${notSentStatus(deps, b) ??
|
|
2884
|
+
// ⚠️ SAID AS WHAT IT IS - see BookingStatus. Neither booked nor failed.
|
|
2885
|
+
(b.status === 'unconfirmed'
|
|
2886
|
+
? 'unconfirmed: whether it was made is not known yet, and a person is confirming it'
|
|
2887
|
+
: b.status)}`,
|
|
2810
2888
|
held ? ` · ${held}` : '',
|
|
2811
2889
|
kinds.length ? ` · asked for ${kinds.map(bare).join(', ')}` : '',
|
|
2812
2890
|
b.quote.coupon
|
|
@@ -2814,7 +2892,9 @@ const bookingLine = (b, noCards) => {
|
|
|
2814
2892
|
? ' (given back)'
|
|
2815
2893
|
: b.couponReturned === false
|
|
2816
2894
|
? b.couponNotReturnedBecause === 'fee'
|
|
2817
|
-
?
|
|
2895
|
+
? notSent
|
|
2896
|
+
? ' (not given back - under the terms the cancellation costs a fee)'
|
|
2897
|
+
: ' (not given back - the cancellation was charged a fee)'
|
|
2818
2898
|
: ' (ran out - not given back)'
|
|
2819
2899
|
: ''}`
|
|
2820
2900
|
: '',
|
|
@@ -3783,6 +3863,18 @@ export const PLAN_DRAWN_ONCE = 'The plan on the card is this one. Something to c
|
|
|
3783
3863
|
export const PLAN_NOT_DRAWN = 'Nothing was drawn: this app shows no cards, so the guest has seen none of this plan. Tell it to them in your reply, day by day - where each day is spent, and each stay with its hotel, dates, price and terms as above. Something to change that you notice now - a sight closed on its day, a day too far from its hotel - is said in that reply, not planned again: the plan is made again only when the guest asks for the change.';
|
|
3784
3864
|
/** A comparison's answer, for a client that shows no cards - see `PLAN_NOT_DRAWN`. */
|
|
3785
3865
|
export const COMPARISON_NOT_DRAWN = "Nothing was drawn: this app shows no cards, so the guest has seen none of this comparison. Compare the hotels in your reply, with each one's price and terms as given here - each is its cheapest room that sleeps the party.";
|
|
3866
|
+
/**
|
|
3867
|
+
* What the model is told where the client DRAWS the card (0.55.7, 总线程 11-05,
|
|
3868
|
+
* robin 「3.ok」): on claude.ai the hotel's card listed its four room types, and the
|
|
3869
|
+
* reply listed them again in a table - breakfast, nightly rate, the stay, the terms.
|
|
3870
|
+
* Said only to a client that draws cards (`ToolContext.drawsCards`), in the text and
|
|
3871
|
+
* in the structure (`cardShows`); a client that draws none is given every room in
|
|
3872
|
+
* words as before (规矩 9).
|
|
3873
|
+
*/
|
|
3874
|
+
export const ON_CARD = {
|
|
3875
|
+
hotels: 'The card shows these hotels, each with its price: reply in a sentence or two on what stands out for the guest, and do not list them again.',
|
|
3876
|
+
rooms: 'The card shows these rooms, each with its price and terms: reply in a sentence or two on what stands out for the guest, and do not list them again.',
|
|
3877
|
+
};
|
|
3786
3878
|
/**
|
|
3787
3879
|
* A list's answer, for a client that shows no cards - see `PLAN_NOT_DRAWN`.
|
|
3788
3880
|
*
|
|
@@ -4986,6 +5078,13 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
4986
5078
|
// never a market, whose home is its groups.
|
|
4987
5079
|
const unnarrowed = !ask.query && !ask.city && !ask.filters && !ask.maxNightly;
|
|
4988
5080
|
const deployed = deps.hotelCount?.() ?? (unnarrowed ? answer.total : 0);
|
|
5081
|
+
// ⚠️ A BACKEND THAT HAS NO PRICE FOR A STAY (11-05; Otaless, whose /rooms is empty
|
|
5082
|
+
// and whose from-price says nothing of taxes - `Hotel.fromNightlyTaxes`) lists a
|
|
5083
|
+
// from-price a night and no stay total, with dates or without. The list must not
|
|
5084
|
+
// promise one ("priced for the nights you choose", "each price is for the whole
|
|
5085
|
+
// stay"); it says what the price is. The card reads the same flag.
|
|
5086
|
+
const fromPricesOnly = hotels.length > 0 &&
|
|
5087
|
+
hotels.every((h) => h.fromNightly && h.fromNightlyTaxes === 'unknown' && !h.forStay && !h.noRoomsForStay);
|
|
4989
5088
|
const fitsOnePage = !deps.backend.suppliers && !deps.vocab.market && deployed >= 1 && deployed <= HOME_LIST_MAX;
|
|
4990
5089
|
// ⚠️ THE SEARCH AS ASKED, `all` INCLUDED. The echo dropped `all`, so every
|
|
4991
5090
|
// re-query the card made from a "Browse all" list - new dates, the default
|
|
@@ -5171,6 +5270,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5171
5270
|
ask: { ...stayArgs },
|
|
5172
5271
|
...(ask.stay ? { stay: ask.stay } : {}),
|
|
5173
5272
|
...(today ? { today } : {}),
|
|
5273
|
+
...(fromPricesOnly ? { fromPricesOnly: true } : {}),
|
|
5174
5274
|
// ⚠️ IN A MARKET, HOW MANY HOTELS EACH GROUP HAS HERE (11-03): the card
|
|
5175
5275
|
// says "from N groups" of the groups that have one - not of every group
|
|
5176
5276
|
// on the market's list, six of which may have none. Only the counts.
|
|
@@ -5251,7 +5351,11 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5251
5351
|
...(answer.refine ? { refine: refineFor(answer.refine, asked.filters, 0) } : {}),
|
|
5252
5352
|
beforeFilters: answer.beforeFilters,
|
|
5253
5353
|
}
|
|
5254
|
-
:
|
|
5354
|
+
: // A market's group asked for and with no hotel here (11-05): the card says
|
|
5355
|
+
// which group, and does not send the guest off to "try a city".
|
|
5356
|
+
asked.filters?.supplier
|
|
5357
|
+
? { ask: echo(sort) }
|
|
5358
|
+
: {}),
|
|
5255
5359
|
},
|
|
5256
5360
|
text: [
|
|
5257
5361
|
widened?.found ? `Nothing matched ${widened.from}, so this looked in ${widened.to}.` : '',
|
|
@@ -5314,19 +5418,34 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5314
5418
|
? `${answer.overBudget} more matched and came in over the ceiling.`
|
|
5315
5419
|
: '',
|
|
5316
5420
|
filtersSaid && filterWords(filtersSaid) ? `Filtered to ${filterWords(filtersSaid)}.` : '',
|
|
5421
|
+
// ⚠️ THE SAME ROOMS ON THE HOTEL OPENED FROM IT (0.55.7, 总线程 11-05): a list with
|
|
5422
|
+
// free cancellation on priced each hotel at its cheapest free-to-cancel room, and
|
|
5423
|
+
// the hotel asked without it opened at a cheaper one that cannot be cancelled. The
|
|
5424
|
+
// card's own press carries them; the model is told to, as one line.
|
|
5425
|
+
filtersSaid && (filtersSaid.freeCancellation || filtersSaid.breakfast || filtersSaid.bed)
|
|
5426
|
+
? `To open one of these, pass ${[
|
|
5427
|
+
filtersSaid.freeCancellation ? '`freeCancellation`' : '',
|
|
5428
|
+
filtersSaid.breakfast ? '`breakfast`' : '',
|
|
5429
|
+
filtersSaid.bed ? `\`bed: "${filtersSaid.bed}"\`` : '',
|
|
5430
|
+
]
|
|
5431
|
+
.filter(Boolean)
|
|
5432
|
+
.join(' and ')} to ${deps.tools.getHotel} too, so its rooms are priced as here.`
|
|
5433
|
+
: '',
|
|
5317
5434
|
// What the prices on a dated list are, in one line - and how many cannot
|
|
5318
5435
|
// be had at all, so a sold-out hotel is not described as a choice.
|
|
5319
5436
|
ask.filters?.hourly && hotels.some((h) => h.forStay?.hours)
|
|
5320
5437
|
? `By the hour, today: each price is that ${deps.vocab.noun}'s cheapest room by the hour, for its hours, taxes included.`
|
|
5321
|
-
: ask.stay
|
|
5322
|
-
? `
|
|
5323
|
-
|
|
5324
|
-
: ''}${
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5438
|
+
: ask.stay && fromPricesOnly
|
|
5439
|
+
? `No price for ${ask.stay.checkIn} to ${ask.stay.checkOut} is available here: each price is the ${deps.vocab.noun}'s from-price a night, not a total for the stay. Say so; do not quote a stay total.`
|
|
5440
|
+
: ask.stay
|
|
5441
|
+
? `Priced for ${ask.stay.checkIn} to ${ask.stay.checkOut}${ask.rooms && ask.rooms > 1 ? `, ${ask.rooms} rooms` : ''}: each price is that ${deps.vocab.noun}'s cheapest room for the whole stay, taxes included${hotels.some((h) => h.noRoomsForStay && h.noRoomsForStayCode !== 'none-on-sale')
|
|
5442
|
+
? `; ${hotels.filter((h) => h.noRoomsForStay && h.noRoomsForStayCode !== 'none-on-sale').length} in this answer have no room for those nights`
|
|
5443
|
+
: ''}${
|
|
5444
|
+
// The other kind, said as its own (U11): other nights will not help.
|
|
5445
|
+
hotels.some((h) => h.noRoomsForStayCode === 'none-on-sale')
|
|
5446
|
+
? `; ${hotels.filter((h) => h.noRoomsForStayCode === 'none-on-sale').length} in this answer ${hotels.filter((h) => h.noRoomsForStayCode === 'none-on-sale').length === 1 ? 'has' : 'have'} no rooms on sale here - ${NOT_SOLD_OUT}`
|
|
5447
|
+
: ''}.`
|
|
5448
|
+
: '',
|
|
5330
5449
|
].filter(Boolean);
|
|
5331
5450
|
// A price on the list that is not booked here (`notSoldHere`): said as on
|
|
5332
5451
|
// the hotel's page, in both - see `notSoldHereWords`.
|
|
@@ -5383,6 +5502,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5383
5502
|
// not a new one - and a place chip keeps the order the guest chose.
|
|
5384
5503
|
ask: echo(applied),
|
|
5385
5504
|
...(ask.stay ? { stay: ask.stay } : {}),
|
|
5505
|
+
...(fromPricesOnly ? { fromPricesOnly: true } : {}),
|
|
5386
5506
|
...(answer.refine
|
|
5387
5507
|
? { refine: refineFor(answer.refine, asked.filters, total, !!ask.stay) }
|
|
5388
5508
|
: {}),
|
|
@@ -5397,6 +5517,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5397
5517
|
// In both, like a plan's: a host with no card may hand its model this
|
|
5398
5518
|
// rather than the text.
|
|
5399
5519
|
...(ctx.noCards ? { notDrawn: LIST_NOT_DRAWN } : {}),
|
|
5520
|
+
...(ctx.drawsCards && hotels.length ? { cardShows: ON_CARD.hotels } : {}),
|
|
5400
5521
|
...(notSold ? { notSoldHere: notSold } : {}),
|
|
5401
5522
|
},
|
|
5402
5523
|
text: [
|
|
@@ -5416,6 +5537,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5416
5537
|
ctx.noCards && hotels.length
|
|
5417
5538
|
? `\n${LIST_NOT_DRAWN}\n${hotels.map(listedInWords).join('\n')}\n`
|
|
5418
5539
|
: '',
|
|
5540
|
+
ctx.drawsCards && hotels.length ? ON_CARD.hotels : '',
|
|
5419
5541
|
forPlans,
|
|
5420
5542
|
]
|
|
5421
5543
|
.filter(Boolean)
|
|
@@ -5609,6 +5731,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5609
5731
|
...(notSold ? { notSoldHere: notSold } : {}),
|
|
5610
5732
|
...(memberRates ? { memberRates } : {}),
|
|
5611
5733
|
...flex.structured,
|
|
5734
|
+
...(ctx.drawsCards && enough.length ? { cardShows: ON_CARD.rooms } : {}),
|
|
5612
5735
|
},
|
|
5613
5736
|
text: [
|
|
5614
5737
|
memberRates?.said ?? '',
|
|
@@ -5620,6 +5743,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5620
5743
|
? `${flex.text} (That is the cheapest room of any kind, not only one with ${termWords(terms)}.)`
|
|
5621
5744
|
: flex.text,
|
|
5622
5745
|
notSold,
|
|
5746
|
+
ctx.drawsCards && enough.length ? ON_CARD.rooms : '',
|
|
5623
5747
|
]
|
|
5624
5748
|
.filter(Boolean)
|
|
5625
5749
|
.join('\n'),
|
|
@@ -5888,7 +6012,8 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5888
6012
|
kind: 'extra',
|
|
5889
6013
|
hotelId,
|
|
5890
6014
|
extraId: extra.id,
|
|
5891
|
-
quantity: clampCount(args.quantity ??
|
|
6015
|
+
quantity: clampCount(args.quantity ??
|
|
6016
|
+
(PRICED_BY_PERSON.has(extra.per) ? partyOnTrip(deps, key, hotelId, args) : 1), MAX_EXTRA_QUANTITY),
|
|
5892
6017
|
...(isDay(str(args.day)) ? { day: str(args.day) } : {}),
|
|
5893
6018
|
};
|
|
5894
6019
|
deps.trips.set(key, line);
|
|
@@ -5963,7 +6088,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5963
6088
|
structured: {
|
|
5964
6089
|
view: 'bookings',
|
|
5965
6090
|
...common(deps, key),
|
|
5966
|
-
bookings: bookings.map(
|
|
6091
|
+
bookings: bookings.map((b) => listedForCard(deps, b)),
|
|
5967
6092
|
count: bookings.length,
|
|
5968
6093
|
// What was looked for, so a card can say THAT did not match rather
|
|
5969
6094
|
// than "nothing in this conversation".
|
|
@@ -5974,7 +6099,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5974
6099
|
text: [
|
|
5975
6100
|
missed ? missLine : '',
|
|
5976
6101
|
bookings.length
|
|
5977
|
-
? bookings.map((b) => bookingLine(b, ctx.noCards)).join('\n')
|
|
6102
|
+
? bookings.map((b) => bookingLine(deps, b, ctx.noCards)).join('\n')
|
|
5978
6103
|
: code
|
|
5979
6104
|
? ''
|
|
5980
6105
|
: // ⚠️ NOT "NO BOOKINGS" when a group did not answer: it may hold them.
|
|
@@ -6072,7 +6197,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6072
6197
|
booking: bookingForCard(step.booking),
|
|
6073
6198
|
total: formatMoney(step.booking.quote.total),
|
|
6074
6199
|
},
|
|
6075
|
-
text: `${step.booking.confirmationCode} was paid on the card page already, so it stays paid there: ${bookingLine(step.booking)}`,
|
|
6200
|
+
text: `${step.booking.confirmationCode} was paid on the card page already, so it stays paid there: ${bookingLine(deps, step.booking)}`,
|
|
6076
6201
|
};
|
|
6077
6202
|
}
|
|
6078
6203
|
return {
|
|
@@ -6339,7 +6464,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6339
6464
|
? {
|
|
6340
6465
|
coupon: {
|
|
6341
6466
|
...preview.coupon,
|
|
6342
|
-
said: couponBackWords(preview.coupon, formatMoney(preview.fee)),
|
|
6467
|
+
said: couponBackWords(preview.coupon, formatMoney(preview.fee), false, holderOf(deps, preview.supplier).notReal),
|
|
6343
6468
|
},
|
|
6344
6469
|
}
|
|
6345
6470
|
: {}),
|
|
@@ -6347,7 +6472,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6347
6472
|
text: [
|
|
6348
6473
|
`Cancelling ${preview.confirmationCode} would cost ${formatMoney(preview.fee)}${preview.fee.minor === 0 ? ' - nothing' : ''}.`,
|
|
6349
6474
|
preview.rule,
|
|
6350
|
-
preview.coupon
|
|
6475
|
+
preview.coupon
|
|
6476
|
+
? couponBackWords(preview.coupon, formatMoney(preview.fee), false, holderOf(deps, preview.supplier).notReal)
|
|
6477
|
+
: '',
|
|
6351
6478
|
preview.cancellable
|
|
6352
6479
|
? `Say the word and it is cancelled (${t.cancelBooking} with \`confirmed\`).`
|
|
6353
6480
|
: '',
|
|
@@ -6408,7 +6535,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6408
6535
|
notReturnedBecause: cancelled.couponNotReturnedBecause ?? 'expired',
|
|
6409
6536
|
}),
|
|
6410
6537
|
};
|
|
6411
|
-
return { ...c, said: couponBackWords(c, formatMoney(charged), true) };
|
|
6538
|
+
return { ...c, said: couponBackWords(c, formatMoney(charged), true, h.notReal) };
|
|
6412
6539
|
})()
|
|
6413
6540
|
: undefined;
|
|
6414
6541
|
return {
|