@curless/hotel-storefront 0.55.6 → 0.55.7
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/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/tools.d.ts +22 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +150 -35
- 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)
|
|
@@ -2245,14 +2283,24 @@ const couponTerms = (c, brand) => [
|
|
|
2245
2283
|
*/
|
|
2246
2284
|
const couponBackWords = (c, fee,
|
|
2247
2285
|
/** Said after the cancellation, not before it. */
|
|
2248
|
-
done = false
|
|
2286
|
+
done = false,
|
|
2287
|
+
/**
|
|
2288
|
+
* The booking was never sent to its group: nothing is charged, and the fee is the
|
|
2289
|
+
* terms' - "this cancellation was charged a fee" beside "nothing is charged" said
|
|
2290
|
+
* money was taken (0.55.7, 总线程 11-05; the card says it the same way).
|
|
2291
|
+
*/
|
|
2292
|
+
notSent = false) => c.returned
|
|
2249
2293
|
? done
|
|
2250
2294
|
? `The coupon ${couponName(c)} is back with the member, usable until ${c.validUntil}: the cancellation was free.`
|
|
2251
2295
|
: `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
2296
|
: c.notReturnedBecause === 'fee'
|
|
2253
|
-
?
|
|
2254
|
-
?
|
|
2255
|
-
|
|
2297
|
+
? notSent
|
|
2298
|
+
? done
|
|
2299
|
+
? `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.`
|
|
2300
|
+
: `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.`
|
|
2301
|
+
: done
|
|
2302
|
+
? `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.`
|
|
2303
|
+
: `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
2304
|
: done
|
|
2257
2305
|
? `The coupon ${couponName(c)} ran out on ${c.validUntil}, so it is not given back.`
|
|
2258
2306
|
: `The coupon ${couponName(c)} it used ran out on ${c.validUntil}, so cancelling does not give it back.`;
|
|
@@ -2798,15 +2846,33 @@ const heldRoomsWords = (b) => b.quote.lines
|
|
|
2798
2846
|
? ', past its free cancellation - cancelling has a fee'
|
|
2799
2847
|
: ''}`)
|
|
2800
2848
|
.join('; ');
|
|
2801
|
-
|
|
2849
|
+
/**
|
|
2850
|
+
* A booking's status said for the model where the booking was never sent to its
|
|
2851
|
+
* group (0.55.7, 总线程 11-05): "requested" - or "confirmed", paid on a page that moved
|
|
2852
|
+
* no money - promised an answer from a group that never heard of it. The card says
|
|
2853
|
+
* the same (`shownStatus`); `undefined` where the status says what it is.
|
|
2854
|
+
*/
|
|
2855
|
+
const notSentStatus = (deps, b) => {
|
|
2856
|
+
const h = holderOf(deps, b.supplier);
|
|
2857
|
+
return h.notReal && ['requested', 'unconfirmed', 'confirmed'].includes(b.status)
|
|
2858
|
+
? `not sent to ${h.name} - nobody confirms it, and no room is held`
|
|
2859
|
+
: undefined;
|
|
2860
|
+
};
|
|
2861
|
+
/** A booking for the card and the model's structure, with `notSentStatus` where there is one. */
|
|
2862
|
+
const listedForCard = (deps, b) => {
|
|
2863
|
+
const said = notSentStatus(deps, b);
|
|
2864
|
+
return { ...bookingForCard(b), ...(said ? { statusSaid: said } : {}) };
|
|
2865
|
+
};
|
|
2866
|
+
const bookingLine = (deps, b, noCards) => {
|
|
2802
2867
|
const kinds = b.requests?.kinds ?? [];
|
|
2803
2868
|
const held = noCards ? heldRoomsWords(b) : '';
|
|
2869
|
+
const notSent = holderOf(deps, b.supplier).notReal;
|
|
2804
2870
|
return [
|
|
2805
|
-
`${b.confirmationCode} · ${withZh(b.hotelName, b.hotelNames, noCards)} · ${stayWords(b.stay, b.hourly)} · ${
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2871
|
+
`${b.confirmationCode} · ${withZh(b.hotelName, b.hotelNames, noCards)} · ${stayWords(b.stay, b.hourly)} · ${notSentStatus(deps, b) ??
|
|
2872
|
+
// ⚠️ SAID AS WHAT IT IS - see BookingStatus. Neither booked nor failed.
|
|
2873
|
+
(b.status === 'unconfirmed'
|
|
2874
|
+
? 'unconfirmed: whether it was made is not known yet, and a person is confirming it'
|
|
2875
|
+
: b.status)}`,
|
|
2810
2876
|
held ? ` · ${held}` : '',
|
|
2811
2877
|
kinds.length ? ` · asked for ${kinds.map(bare).join(', ')}` : '',
|
|
2812
2878
|
b.quote.coupon
|
|
@@ -2814,7 +2880,9 @@ const bookingLine = (b, noCards) => {
|
|
|
2814
2880
|
? ' (given back)'
|
|
2815
2881
|
: b.couponReturned === false
|
|
2816
2882
|
? b.couponNotReturnedBecause === 'fee'
|
|
2817
|
-
?
|
|
2883
|
+
? notSent
|
|
2884
|
+
? ' (not given back - under the terms the cancellation costs a fee)'
|
|
2885
|
+
: ' (not given back - the cancellation was charged a fee)'
|
|
2818
2886
|
: ' (ran out - not given back)'
|
|
2819
2887
|
: ''}`
|
|
2820
2888
|
: '',
|
|
@@ -3783,6 +3851,18 @@ export const PLAN_DRAWN_ONCE = 'The plan on the card is this one. Something to c
|
|
|
3783
3851
|
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
3852
|
/** A comparison's answer, for a client that shows no cards - see `PLAN_NOT_DRAWN`. */
|
|
3785
3853
|
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.";
|
|
3854
|
+
/**
|
|
3855
|
+
* What the model is told where the client DRAWS the card (0.55.7, 总线程 11-05,
|
|
3856
|
+
* robin 「3.ok」): on claude.ai the hotel's card listed its four room types, and the
|
|
3857
|
+
* reply listed them again in a table - breakfast, nightly rate, the stay, the terms.
|
|
3858
|
+
* Said only to a client that draws cards (`ToolContext.drawsCards`), in the text and
|
|
3859
|
+
* in the structure (`cardShows`); a client that draws none is given every room in
|
|
3860
|
+
* words as before (规矩 9).
|
|
3861
|
+
*/
|
|
3862
|
+
export const ON_CARD = {
|
|
3863
|
+
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.',
|
|
3864
|
+
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.',
|
|
3865
|
+
};
|
|
3786
3866
|
/**
|
|
3787
3867
|
* A list's answer, for a client that shows no cards - see `PLAN_NOT_DRAWN`.
|
|
3788
3868
|
*
|
|
@@ -4986,6 +5066,13 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
4986
5066
|
// never a market, whose home is its groups.
|
|
4987
5067
|
const unnarrowed = !ask.query && !ask.city && !ask.filters && !ask.maxNightly;
|
|
4988
5068
|
const deployed = deps.hotelCount?.() ?? (unnarrowed ? answer.total : 0);
|
|
5069
|
+
// ⚠️ A BACKEND THAT HAS NO PRICE FOR A STAY (11-05; Otaless, whose /rooms is empty
|
|
5070
|
+
// and whose from-price says nothing of taxes - `Hotel.fromNightlyTaxes`) lists a
|
|
5071
|
+
// from-price a night and no stay total, with dates or without. The list must not
|
|
5072
|
+
// promise one ("priced for the nights you choose", "each price is for the whole
|
|
5073
|
+
// stay"); it says what the price is. The card reads the same flag.
|
|
5074
|
+
const fromPricesOnly = hotels.length > 0 &&
|
|
5075
|
+
hotels.every((h) => h.fromNightly && h.fromNightlyTaxes === 'unknown' && !h.forStay && !h.noRoomsForStay);
|
|
4989
5076
|
const fitsOnePage = !deps.backend.suppliers && !deps.vocab.market && deployed >= 1 && deployed <= HOME_LIST_MAX;
|
|
4990
5077
|
// ⚠️ THE SEARCH AS ASKED, `all` INCLUDED. The echo dropped `all`, so every
|
|
4991
5078
|
// re-query the card made from a "Browse all" list - new dates, the default
|
|
@@ -5171,6 +5258,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5171
5258
|
ask: { ...stayArgs },
|
|
5172
5259
|
...(ask.stay ? { stay: ask.stay } : {}),
|
|
5173
5260
|
...(today ? { today } : {}),
|
|
5261
|
+
...(fromPricesOnly ? { fromPricesOnly: true } : {}),
|
|
5174
5262
|
// ⚠️ IN A MARKET, HOW MANY HOTELS EACH GROUP HAS HERE (11-03): the card
|
|
5175
5263
|
// says "from N groups" of the groups that have one - not of every group
|
|
5176
5264
|
// on the market's list, six of which may have none. Only the counts.
|
|
@@ -5251,7 +5339,11 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5251
5339
|
...(answer.refine ? { refine: refineFor(answer.refine, asked.filters, 0) } : {}),
|
|
5252
5340
|
beforeFilters: answer.beforeFilters,
|
|
5253
5341
|
}
|
|
5254
|
-
:
|
|
5342
|
+
: // A market's group asked for and with no hotel here (11-05): the card says
|
|
5343
|
+
// which group, and does not send the guest off to "try a city".
|
|
5344
|
+
asked.filters?.supplier
|
|
5345
|
+
? { ask: echo(sort) }
|
|
5346
|
+
: {}),
|
|
5255
5347
|
},
|
|
5256
5348
|
text: [
|
|
5257
5349
|
widened?.found ? `Nothing matched ${widened.from}, so this looked in ${widened.to}.` : '',
|
|
@@ -5314,19 +5406,34 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5314
5406
|
? `${answer.overBudget} more matched and came in over the ceiling.`
|
|
5315
5407
|
: '',
|
|
5316
5408
|
filtersSaid && filterWords(filtersSaid) ? `Filtered to ${filterWords(filtersSaid)}.` : '',
|
|
5409
|
+
// ⚠️ THE SAME ROOMS ON THE HOTEL OPENED FROM IT (0.55.7, 总线程 11-05): a list with
|
|
5410
|
+
// free cancellation on priced each hotel at its cheapest free-to-cancel room, and
|
|
5411
|
+
// the hotel asked without it opened at a cheaper one that cannot be cancelled. The
|
|
5412
|
+
// card's own press carries them; the model is told to, as one line.
|
|
5413
|
+
filtersSaid && (filtersSaid.freeCancellation || filtersSaid.breakfast || filtersSaid.bed)
|
|
5414
|
+
? `To open one of these, pass ${[
|
|
5415
|
+
filtersSaid.freeCancellation ? '`freeCancellation`' : '',
|
|
5416
|
+
filtersSaid.breakfast ? '`breakfast`' : '',
|
|
5417
|
+
filtersSaid.bed ? `\`bed: "${filtersSaid.bed}"\`` : '',
|
|
5418
|
+
]
|
|
5419
|
+
.filter(Boolean)
|
|
5420
|
+
.join(' and ')} to ${deps.tools.getHotel} too, so its rooms are priced as here.`
|
|
5421
|
+
: '',
|
|
5317
5422
|
// What the prices on a dated list are, in one line - and how many cannot
|
|
5318
5423
|
// be had at all, so a sold-out hotel is not described as a choice.
|
|
5319
5424
|
ask.filters?.hourly && hotels.some((h) => h.forStay?.hours)
|
|
5320
5425
|
? `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
|
-
|
|
5426
|
+
: ask.stay && fromPricesOnly
|
|
5427
|
+
? `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.`
|
|
5428
|
+
: ask.stay
|
|
5429
|
+
? `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')
|
|
5430
|
+
? `; ${hotels.filter((h) => h.noRoomsForStay && h.noRoomsForStayCode !== 'none-on-sale').length} in this answer have no room for those nights`
|
|
5431
|
+
: ''}${
|
|
5432
|
+
// The other kind, said as its own (U11): other nights will not help.
|
|
5433
|
+
hotels.some((h) => h.noRoomsForStayCode === 'none-on-sale')
|
|
5434
|
+
? `; ${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}`
|
|
5435
|
+
: ''}.`
|
|
5436
|
+
: '',
|
|
5330
5437
|
].filter(Boolean);
|
|
5331
5438
|
// A price on the list that is not booked here (`notSoldHere`): said as on
|
|
5332
5439
|
// the hotel's page, in both - see `notSoldHereWords`.
|
|
@@ -5383,6 +5490,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5383
5490
|
// not a new one - and a place chip keeps the order the guest chose.
|
|
5384
5491
|
ask: echo(applied),
|
|
5385
5492
|
...(ask.stay ? { stay: ask.stay } : {}),
|
|
5493
|
+
...(fromPricesOnly ? { fromPricesOnly: true } : {}),
|
|
5386
5494
|
...(answer.refine
|
|
5387
5495
|
? { refine: refineFor(answer.refine, asked.filters, total, !!ask.stay) }
|
|
5388
5496
|
: {}),
|
|
@@ -5397,6 +5505,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5397
5505
|
// In both, like a plan's: a host with no card may hand its model this
|
|
5398
5506
|
// rather than the text.
|
|
5399
5507
|
...(ctx.noCards ? { notDrawn: LIST_NOT_DRAWN } : {}),
|
|
5508
|
+
...(ctx.drawsCards && hotels.length ? { cardShows: ON_CARD.hotels } : {}),
|
|
5400
5509
|
...(notSold ? { notSoldHere: notSold } : {}),
|
|
5401
5510
|
},
|
|
5402
5511
|
text: [
|
|
@@ -5416,6 +5525,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5416
5525
|
ctx.noCards && hotels.length
|
|
5417
5526
|
? `\n${LIST_NOT_DRAWN}\n${hotels.map(listedInWords).join('\n')}\n`
|
|
5418
5527
|
: '',
|
|
5528
|
+
ctx.drawsCards && hotels.length ? ON_CARD.hotels : '',
|
|
5419
5529
|
forPlans,
|
|
5420
5530
|
]
|
|
5421
5531
|
.filter(Boolean)
|
|
@@ -5609,6 +5719,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5609
5719
|
...(notSold ? { notSoldHere: notSold } : {}),
|
|
5610
5720
|
...(memberRates ? { memberRates } : {}),
|
|
5611
5721
|
...flex.structured,
|
|
5722
|
+
...(ctx.drawsCards && enough.length ? { cardShows: ON_CARD.rooms } : {}),
|
|
5612
5723
|
},
|
|
5613
5724
|
text: [
|
|
5614
5725
|
memberRates?.said ?? '',
|
|
@@ -5620,6 +5731,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5620
5731
|
? `${flex.text} (That is the cheapest room of any kind, not only one with ${termWords(terms)}.)`
|
|
5621
5732
|
: flex.text,
|
|
5622
5733
|
notSold,
|
|
5734
|
+
ctx.drawsCards && enough.length ? ON_CARD.rooms : '',
|
|
5623
5735
|
]
|
|
5624
5736
|
.filter(Boolean)
|
|
5625
5737
|
.join('\n'),
|
|
@@ -5888,7 +6000,8 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5888
6000
|
kind: 'extra',
|
|
5889
6001
|
hotelId,
|
|
5890
6002
|
extraId: extra.id,
|
|
5891
|
-
quantity: clampCount(args.quantity ??
|
|
6003
|
+
quantity: clampCount(args.quantity ??
|
|
6004
|
+
(PRICED_BY_PERSON.has(extra.per) ? partyOnTrip(deps, key, hotelId, args) : 1), MAX_EXTRA_QUANTITY),
|
|
5892
6005
|
...(isDay(str(args.day)) ? { day: str(args.day) } : {}),
|
|
5893
6006
|
};
|
|
5894
6007
|
deps.trips.set(key, line);
|
|
@@ -5963,7 +6076,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5963
6076
|
structured: {
|
|
5964
6077
|
view: 'bookings',
|
|
5965
6078
|
...common(deps, key),
|
|
5966
|
-
bookings: bookings.map(
|
|
6079
|
+
bookings: bookings.map((b) => listedForCard(deps, b)),
|
|
5967
6080
|
count: bookings.length,
|
|
5968
6081
|
// What was looked for, so a card can say THAT did not match rather
|
|
5969
6082
|
// than "nothing in this conversation".
|
|
@@ -5974,7 +6087,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5974
6087
|
text: [
|
|
5975
6088
|
missed ? missLine : '',
|
|
5976
6089
|
bookings.length
|
|
5977
|
-
? bookings.map((b) => bookingLine(b, ctx.noCards)).join('\n')
|
|
6090
|
+
? bookings.map((b) => bookingLine(deps, b, ctx.noCards)).join('\n')
|
|
5978
6091
|
: code
|
|
5979
6092
|
? ''
|
|
5980
6093
|
: // ⚠️ NOT "NO BOOKINGS" when a group did not answer: it may hold them.
|
|
@@ -6072,7 +6185,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6072
6185
|
booking: bookingForCard(step.booking),
|
|
6073
6186
|
total: formatMoney(step.booking.quote.total),
|
|
6074
6187
|
},
|
|
6075
|
-
text: `${step.booking.confirmationCode} was paid on the card page already, so it stays paid there: ${bookingLine(step.booking)}`,
|
|
6188
|
+
text: `${step.booking.confirmationCode} was paid on the card page already, so it stays paid there: ${bookingLine(deps, step.booking)}`,
|
|
6076
6189
|
};
|
|
6077
6190
|
}
|
|
6078
6191
|
return {
|
|
@@ -6339,7 +6452,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6339
6452
|
? {
|
|
6340
6453
|
coupon: {
|
|
6341
6454
|
...preview.coupon,
|
|
6342
|
-
said: couponBackWords(preview.coupon, formatMoney(preview.fee)),
|
|
6455
|
+
said: couponBackWords(preview.coupon, formatMoney(preview.fee), false, holderOf(deps, preview.supplier).notReal),
|
|
6343
6456
|
},
|
|
6344
6457
|
}
|
|
6345
6458
|
: {}),
|
|
@@ -6347,7 +6460,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6347
6460
|
text: [
|
|
6348
6461
|
`Cancelling ${preview.confirmationCode} would cost ${formatMoney(preview.fee)}${preview.fee.minor === 0 ? ' - nothing' : ''}.`,
|
|
6349
6462
|
preview.rule,
|
|
6350
|
-
preview.coupon
|
|
6463
|
+
preview.coupon
|
|
6464
|
+
? couponBackWords(preview.coupon, formatMoney(preview.fee), false, holderOf(deps, preview.supplier).notReal)
|
|
6465
|
+
: '',
|
|
6351
6466
|
preview.cancellable
|
|
6352
6467
|
? `Say the word and it is cancelled (${t.cancelBooking} with \`confirmed\`).`
|
|
6353
6468
|
: '',
|
|
@@ -6408,7 +6523,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6408
6523
|
notReturnedBecause: cancelled.couponNotReturnedBecause ?? 'expired',
|
|
6409
6524
|
}),
|
|
6410
6525
|
};
|
|
6411
|
-
return { ...c, said: couponBackWords(c, formatMoney(charged), true) };
|
|
6526
|
+
return { ...c, said: couponBackWords(c, formatMoney(charged), true, h.notReal) };
|
|
6412
6527
|
})()
|
|
6413
6528
|
: undefined;
|
|
6414
6529
|
return {
|