@curless/hotel-storefront 0.23.2 → 0.24.1
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/README.md +35 -11
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts +2 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts.map +1 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.js +3 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.js.map +1 -0
- package/dist/__tests__/huazhu-backend.test.d.ts +2 -0
- package/dist/__tests__/huazhu-backend.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-backend.test.js +434 -0
- package/dist/__tests__/huazhu-backend.test.js.map +1 -0
- package/dist/__tests__/huazhu-cancel-policy.test.d.ts +2 -0
- package/dist/__tests__/huazhu-cancel-policy.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-cancel-policy.test.js +82 -0
- package/dist/__tests__/huazhu-cancel-policy.test.js.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.js +172 -0
- package/dist/__tests__/huazhu-sandbox.test.js.map +1 -0
- package/dist/__tests__/huazhu-sellable.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sellable.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sellable.test.js +192 -0
- package/dist/__tests__/huazhu-sellable.test.js.map +1 -0
- package/dist/__tests__/server.test.js +129 -4
- package/dist/__tests__/server.test.js.map +1 -1
- package/dist/__tests__/tool-descriptions.test.js +85 -7
- package/dist/__tests__/tool-descriptions.test.js.map +1 -1
- package/dist/backend.d.ts +27 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +7 -0
- package/dist/backend.js.map +1 -1
- 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/config.d.ts +64 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/facilities.d.ts.map +1 -1
- package/dist/facilities.js +7 -5
- package/dist/facilities.js.map +1 -1
- package/dist/huazhu/backend.d.ts +40 -0
- package/dist/huazhu/backend.d.ts.map +1 -0
- package/dist/huazhu/backend.js +1318 -0
- package/dist/huazhu/backend.js.map +1 -0
- package/dist/huazhu/bookings.d.ts +81 -0
- package/dist/huazhu/bookings.d.ts.map +1 -0
- package/dist/huazhu/bookings.js +189 -0
- package/dist/huazhu/bookings.js.map +1 -0
- package/dist/huazhu/cancel-policy.d.ts +69 -0
- package/dist/huazhu/cancel-policy.d.ts.map +1 -0
- package/dist/huazhu/cancel-policy.js +201 -0
- package/dist/huazhu/cancel-policy.js.map +1 -0
- package/dist/huazhu/catalog.d.ts +75 -0
- package/dist/huazhu/catalog.d.ts.map +1 -0
- package/dist/huazhu/catalog.js +251 -0
- package/dist/huazhu/catalog.js.map +1 -0
- package/dist/huazhu/check.d.ts +5 -0
- package/dist/huazhu/check.d.ts.map +1 -0
- package/dist/huazhu/check.js +170 -0
- package/dist/huazhu/check.js.map +1 -0
- package/dist/huazhu/client.d.ts +100 -0
- package/dist/huazhu/client.d.ts.map +1 -0
- package/dist/huazhu/client.js +232 -0
- package/dist/huazhu/client.js.map +1 -0
- package/dist/huazhu/fake.d.ts +64 -0
- package/dist/huazhu/fake.d.ts.map +1 -0
- package/dist/huazhu/fake.js +365 -0
- package/dist/huazhu/fake.js.map +1 -0
- package/dist/huazhu/sandbox.d.ts +26 -0
- package/dist/huazhu/sandbox.d.ts.map +1 -0
- package/dist/huazhu/sandbox.js +289 -0
- package/dist/huazhu/sandbox.js.map +1 -0
- package/dist/huazhu/sellable.d.ts +81 -0
- package/dist/huazhu/sellable.d.ts.map +1 -0
- package/dist/huazhu/sellable.js +219 -0
- package/dist/huazhu/sellable.js.map +1 -0
- package/dist/huazhu/wire.d.ts +389 -0
- package/dist/huazhu/wire.d.ts.map +1 -0
- package/dist/huazhu/wire.js +72 -0
- package/dist/huazhu/wire.js.map +1 -0
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/legal.d.ts +4 -3
- package/dist/legal.d.ts.map +1 -1
- package/dist/legal.js +71 -30
- package/dist/legal.js.map +1 -1
- package/dist/lib.d.ts +8 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +8 -1
- package/dist/lib.js.map +1 -1
- package/dist/server.d.ts +20 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +132 -28
- package/dist/server.js.map +1 -1
- package/dist/surface.d.ts +2 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +42 -10
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts +33 -20
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +122 -11
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +21 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vocab.d.ts +5 -0
- package/dist/vocab.d.ts.map +1 -1
- package/dist/vocab.js +2 -1
- package/dist/vocab.js.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_PAGE, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, } from './backend.js';
|
|
1
|
+
import { DEFAULT_PAGE, FULL_CAPABILITIES, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, } from './backend.js';
|
|
2
2
|
import { isDay, nights as nightsBetween } from './dates.js';
|
|
3
3
|
import { FACILITIES } from './facilities.js';
|
|
4
4
|
import { DESCRIPTION_MAX, POLICY_MAX, hotelText } from './fence.js';
|
|
@@ -63,7 +63,69 @@ const cancels = (title, openWorld) => ({
|
|
|
63
63
|
const DATE = (what) => ({ type: 'string', description: `${what}, as YYYY-MM-DD` });
|
|
64
64
|
/** How many ${nounPlural} one look-up reads - a city's worth, and what a comparison can hold and then some. */
|
|
65
65
|
export const LOOK_UP_MAX = 6;
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The tools this deployment offers: every tool, less the ones its backend
|
|
68
|
+
* cannot do, and with the descriptions of the rest saying only what is true
|
|
69
|
+
* of it - see `BackendCapabilities`.
|
|
70
|
+
*/
|
|
71
|
+
export const buildTools = (n, v, caps = FULL_CAPABILITIES) => forCapabilities(allTools(n, v), n, v, caps);
|
|
72
|
+
/** The tool names a backend with these capabilities leaves out. */
|
|
73
|
+
export const withheldTools = (n, caps) => new Set([
|
|
74
|
+
...(caps.members ? [] : [n.signIn, n.signOut, n.forgetPreferences]),
|
|
75
|
+
...(caps.extras ? [] : [n.listExtras]),
|
|
76
|
+
...(caps.changes || caps.extras ? [] : [n.previewChange, n.changeBooking]),
|
|
77
|
+
]);
|
|
78
|
+
const forCapabilities = (tools, n, v, caps) => {
|
|
79
|
+
const withheld = withheldTools(n, caps);
|
|
80
|
+
const without = (schema, ...keys) => {
|
|
81
|
+
const s = schema;
|
|
82
|
+
const properties = { ...(s.properties ?? {}) };
|
|
83
|
+
for (const k of keys)
|
|
84
|
+
delete properties[k];
|
|
85
|
+
return { ...s, properties };
|
|
86
|
+
};
|
|
87
|
+
return tools
|
|
88
|
+
.filter((t) => !withheld.has(t.name))
|
|
89
|
+
.map((t) => {
|
|
90
|
+
let out = t;
|
|
91
|
+
if (!caps.extras) {
|
|
92
|
+
if (t.name === n.addToTrip) {
|
|
93
|
+
out = {
|
|
94
|
+
...out,
|
|
95
|
+
description: `Puts room nights into the trip and returns the whole trip, priced. A room line needs the ${v.noun}, the room type and the dates; \`rooms\` books more than one of that room type. A line is replaced, not stacked: sending the same room and dates again sets that line's room count, and 0 removes it.`,
|
|
96
|
+
inputSchema: without(out.inputSchema, 'extra', 'quantity'),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (t.name === n.viewTrip) {
|
|
100
|
+
out = {
|
|
101
|
+
...out,
|
|
102
|
+
description: 'The trip as it stands: every room line with its own total, the taxes and fees, and the all-in total, laid out day by day on the card.',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (t.name === n.lookUp) {
|
|
106
|
+
out = {
|
|
107
|
+
...out,
|
|
108
|
+
description: out.description.replace(', its add-ons with their ids and prices', ''),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (t.name === n.itinerary) {
|
|
112
|
+
out = JSON.parse(JSON.stringify(out).replaceAll(' and add-ons', ''));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (t.name === n.requestBooking) {
|
|
116
|
+
if (!caps.members)
|
|
117
|
+
out = { ...out, inputSchema: without(out.inputSchema, 'rememberRequests') };
|
|
118
|
+
if (caps.booking === 'pay-at-hotel') {
|
|
119
|
+
out = {
|
|
120
|
+
...out,
|
|
121
|
+
description: `Books the trip's rooms with ${v.brand} for a named guest: each stay becomes the hotel's own reservation, with its own confirmation code, paid at the hotel. No payment is taken here and no card is collected. All of the trip is booked or none of it. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests the desk tries to meet.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const allTools = (n, v) => {
|
|
67
129
|
const open = !v.demo;
|
|
68
130
|
const stayProps = {
|
|
69
131
|
checkIn: DATE('The arrival date'),
|
|
@@ -578,6 +640,7 @@ const TRIP_SCHEMA = {
|
|
|
578
640
|
view: { enum: ['trip', 'needs-dates', 'error'] },
|
|
579
641
|
brand: STR,
|
|
580
642
|
toolNames: OBJ,
|
|
643
|
+
bookingMode: STR,
|
|
581
644
|
vocab: OBJ,
|
|
582
645
|
demo: BOOL,
|
|
583
646
|
demoNote: STR,
|
|
@@ -601,6 +664,7 @@ const CANCEL_SCHEMA = {
|
|
|
601
664
|
view: { enum: ['cancellation-preview', 'cancelled', 'booking-not-found'] },
|
|
602
665
|
brand: STR,
|
|
603
666
|
toolNames: OBJ,
|
|
667
|
+
bookingMode: STR,
|
|
604
668
|
demo: BOOL,
|
|
605
669
|
demoNote: STR,
|
|
606
670
|
booking: OBJ,
|
|
@@ -619,6 +683,7 @@ const CHANGE_SCHEMA = {
|
|
|
619
683
|
view: { enum: ['change-preview', 'changed', 'booking-not-found'] },
|
|
620
684
|
brand: STR,
|
|
621
685
|
toolNames: OBJ,
|
|
686
|
+
bookingMode: STR,
|
|
622
687
|
demo: BOOL,
|
|
623
688
|
demoNote: STR,
|
|
624
689
|
booking: OBJ,
|
|
@@ -641,6 +706,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
641
706
|
view: { enum: ['hotels', 'no-hotels', 'destinations'] },
|
|
642
707
|
brand: STR,
|
|
643
708
|
toolNames: OBJ,
|
|
709
|
+
bookingMode: STR,
|
|
644
710
|
vocab: OBJ,
|
|
645
711
|
demo: BOOL,
|
|
646
712
|
demoNote: STR,
|
|
@@ -669,6 +735,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
669
735
|
view: { enum: ['hotel', 'no-rooms', 'hotel-not-found'] },
|
|
670
736
|
brand: STR,
|
|
671
737
|
toolNames: OBJ,
|
|
738
|
+
bookingMode: STR,
|
|
672
739
|
vocab: OBJ,
|
|
673
740
|
demo: BOOL,
|
|
674
741
|
demoNote: STR,
|
|
@@ -693,6 +760,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
693
760
|
view: { enum: ['extras', 'hotel-not-found'] },
|
|
694
761
|
brand: STR,
|
|
695
762
|
toolNames: OBJ,
|
|
763
|
+
bookingMode: STR,
|
|
696
764
|
demo: BOOL,
|
|
697
765
|
demoNote: STR,
|
|
698
766
|
hotel: OBJ,
|
|
@@ -709,6 +777,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
709
777
|
view: { enum: ['booking', 'needs-guest', 'empty-trip', 'error'] },
|
|
710
778
|
brand: STR,
|
|
711
779
|
toolNames: OBJ,
|
|
780
|
+
bookingMode: STR,
|
|
712
781
|
demo: BOOL,
|
|
713
782
|
demoNote: STR,
|
|
714
783
|
booking: OBJ,
|
|
@@ -729,6 +798,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
729
798
|
view: { const: 'bookings' },
|
|
730
799
|
brand: STR,
|
|
731
800
|
toolNames: OBJ,
|
|
801
|
+
bookingMode: STR,
|
|
732
802
|
demo: BOOL,
|
|
733
803
|
demoNote: STR,
|
|
734
804
|
bookings: ARR,
|
|
@@ -746,6 +816,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
746
816
|
view: { enum: ['comparison', 'nothing-to-compare'] },
|
|
747
817
|
brand: STR,
|
|
748
818
|
toolNames: OBJ,
|
|
819
|
+
bookingMode: STR,
|
|
749
820
|
demo: BOOL,
|
|
750
821
|
demoNote: STR,
|
|
751
822
|
title: STR,
|
|
@@ -764,6 +835,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
764
835
|
view: { enum: ['itinerary', 'empty-itinerary'] },
|
|
765
836
|
brand: STR,
|
|
766
837
|
toolNames: OBJ,
|
|
838
|
+
bookingMode: STR,
|
|
767
839
|
demo: BOOL,
|
|
768
840
|
demoNote: STR,
|
|
769
841
|
title: STR,
|
|
@@ -782,6 +854,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
782
854
|
view: { enum: ['member', 'member-not-found'] },
|
|
783
855
|
brand: STR,
|
|
784
856
|
toolNames: OBJ,
|
|
857
|
+
bookingMode: STR,
|
|
785
858
|
demo: BOOL,
|
|
786
859
|
demoNote: STR,
|
|
787
860
|
member: OBJ,
|
|
@@ -799,6 +872,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
799
872
|
view: { enum: ['member', 'no-member'] },
|
|
800
873
|
brand: STR,
|
|
801
874
|
toolNames: OBJ,
|
|
875
|
+
bookingMode: STR,
|
|
802
876
|
member: OBJ,
|
|
803
877
|
},
|
|
804
878
|
required: ['view'],
|
|
@@ -811,6 +885,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
811
885
|
view: { enum: ['look-up', 'nothing-found'] },
|
|
812
886
|
brand: STR,
|
|
813
887
|
toolNames: OBJ,
|
|
888
|
+
bookingMode: STR,
|
|
814
889
|
demo: BOOL,
|
|
815
890
|
hotels: ARR,
|
|
816
891
|
stay: OBJ,
|
|
@@ -825,6 +900,7 @@ export const buildOutputSchemas = (n) => ({
|
|
|
825
900
|
view: { enum: ['policy', 'policies', 'policy-missing', 'hotel-not-found'] },
|
|
826
901
|
brand: STR,
|
|
827
902
|
toolNames: OBJ,
|
|
903
|
+
bookingMode: STR,
|
|
828
904
|
demo: BOOL,
|
|
829
905
|
hotel: OBJ,
|
|
830
906
|
topic: STR,
|
|
@@ -872,10 +948,32 @@ const memberBadge = (deps, key) => {
|
|
|
872
948
|
}
|
|
873
949
|
: { signedIn: false };
|
|
874
950
|
};
|
|
951
|
+
/**
|
|
952
|
+
* What was made, in one sentence - which depends on what asking MAKES here.
|
|
953
|
+
*
|
|
954
|
+
* ⚠️ HELD IS NOT BOOKED, AND BOOKED IS NOT PAID. The demonstration holds rooms
|
|
955
|
+
* until a moment and then lets them go; a group's own system makes its own
|
|
956
|
+
* reservation, kept until cancelled and paid at the hotel. Saying "held until"
|
|
957
|
+
* of a reservation invents a deadline, and saying "booked" of a hold invents a
|
|
958
|
+
* room - and a booking whose confirmation has not come back yet is neither,
|
|
959
|
+
* and is said as exactly that.
|
|
960
|
+
*/
|
|
961
|
+
const bookedLine = (deps, b) => {
|
|
962
|
+
const total = formatMoney(b.quote.total);
|
|
963
|
+
if ((deps.backend.capabilities ?? FULL_CAPABILITIES).booking === 'pay-at-hotel') {
|
|
964
|
+
return b.status === 'requested'
|
|
965
|
+
? `${total} all in, to be paid at the hotel. ${deps.vocab.brand} has not confirmed this booking yet - that takes up to eight minutes, and ${deps.tools.listBookings} shows when it has. Nothing has been charged.`
|
|
966
|
+
: `${total} all in, paid at the hotel. **Nothing has been charged here** - this is ${deps.vocab.brand}'s own reservation, kept until it is cancelled, and ${b.guest.name} pays at check-in.`;
|
|
967
|
+
}
|
|
968
|
+
return `${total} all in. **Nothing has been charged** - the rooms are held until ${b.holdUntil?.slice(0, 16).replace('T', ' ')} UTC, and ${b.guest.name} presents a card at check-in.`;
|
|
969
|
+
};
|
|
875
970
|
const common = (deps, key) => ({
|
|
876
971
|
brand: deps.vocab.brand,
|
|
877
972
|
...(deps.logoUrl ? { logoUrl: deps.logoUrl } : {}),
|
|
878
|
-
toolNames: deps.
|
|
973
|
+
toolNames: deps.offered
|
|
974
|
+
? Object.fromEntries(Object.entries(deps.tools).filter(([, name]) => deps.offered?.has(name)))
|
|
975
|
+
: deps.tools,
|
|
976
|
+
bookingMode: (deps.backend.capabilities ?? FULL_CAPABILITIES).booking,
|
|
879
977
|
vocab: { noun: deps.vocab.noun, nounPlural: deps.vocab.nounPlural },
|
|
880
978
|
...(deps.vocab.demo ? { demo: true, demoNote: DEMO_NOTE } : { demo: false }),
|
|
881
979
|
...(key ? memberBadge(deps, key) : {}),
|
|
@@ -1882,8 +1980,14 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
1882
1980
|
// which narrowing would make the question answerable.
|
|
1883
1981
|
const oneCurrency = answer.currencies.length === 1;
|
|
1884
1982
|
const wantedPrice = sort === 'price-low' || sort === 'price-high';
|
|
1885
|
-
const applied = wantedPrice && !oneCurrency ? 'group' : sort;
|
|
1983
|
+
const applied = (wantedPrice && !oneCurrency) || answer.ignored?.sort ? 'group' : sort;
|
|
1984
|
+
// Room terms the backend could not apply are not "filtered to".
|
|
1985
|
+
const filtersSaid = asked.filters && answer.ignored?.roomTerms
|
|
1986
|
+
? { ...asked.filters, freeCancellation: undefined, breakfast: undefined }
|
|
1987
|
+
: asked.filters;
|
|
1886
1988
|
const priceNotes = [
|
|
1989
|
+
// What the backend was asked and did not do, in its own words.
|
|
1990
|
+
...(answer.unapplied ?? []),
|
|
1887
1991
|
wantedPrice && !oneCurrency
|
|
1888
1992
|
? `These are quoted in ${answer.currencies.length} currencies (${answer.currencies.join(', ')}), and no rate is applied here, so they are not in price order — one city or one country gives one currency.`
|
|
1889
1993
|
: '',
|
|
@@ -1893,7 +1997,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
1893
1997
|
answer.overBudget && hotels.length
|
|
1894
1998
|
? `${answer.overBudget} more matched and came in over the ceiling.`
|
|
1895
1999
|
: '',
|
|
1896
|
-
|
|
2000
|
+
filtersSaid && filterWords(filtersSaid) ? `Filtered to ${filterWords(filtersSaid)}.` : '',
|
|
1897
2001
|
// What the prices on a dated list are, in one line - and how many cannot
|
|
1898
2002
|
// be had at all, so a sold-out hotel is not described as a choice.
|
|
1899
2003
|
ask.stay
|
|
@@ -2054,7 +2158,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
2054
2158
|
},
|
|
2055
2159
|
text: [
|
|
2056
2160
|
`**${h.name}** · ${enough.length} room ${enough.length === 1 ? 'type' : 'types'} for ${stay.checkIn} to ${stay.checkOut}, ${stay.guests} guests${roomsWanted > 1 ? `, ${roomsWanted} rooms` : ''}.`,
|
|
2057
|
-
...enough.map((r) => `- ${r.name} — ${formatMoney(r.nightly)} a night, ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${roomsWanted > 1 ? ` (${formatMoney(multiplyMoney(r.stayTotal, roomsWanted))} for ${roomsWanted} rooms)` : ''}${r.freeCancellationUntil ? `, free to cancel until ${r.freeCancellationUntil.slice(0, 10)}` : r.refundable ? ', the free cancellation window has closed - cancelling
|
|
2161
|
+
...enough.map((r) => `- ${r.name} — ${formatMoney(r.nightly)} a night, ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${roomsWanted > 1 ? ` (${formatMoney(multiplyMoney(r.stayTotal, roomsWanted))} for ${roomsWanted} rooms)` : ''}${r.freeCancellationUntil ? `, free to cancel until ${r.freeCancellationUntil.slice(0, 10)}` : r.refundable ? ', the free cancellation window has closed - cancelling now has a fee' : ', non-refundable'}`),
|
|
2058
2162
|
flex.text,
|
|
2059
2163
|
deps.vocab.demo ? DEMO_NOTE : '',
|
|
2060
2164
|
]
|
|
@@ -2270,7 +2374,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
2270
2374
|
error: 'no-such-extra',
|
|
2271
2375
|
query: extraRef,
|
|
2272
2376
|
},
|
|
2273
|
-
text:
|
|
2377
|
+
text: (deps.backend.capabilities ?? FULL_CAPABILITIES).extras
|
|
2378
|
+
? `${detail.hotel.name} does not offer that. ${t.listExtras} lists what it has.`
|
|
2379
|
+
: `${deps.vocab.brand} ${deps.vocab.nounPlural} sell rooms only here - there are no add-ons.`,
|
|
2274
2380
|
};
|
|
2275
2381
|
}
|
|
2276
2382
|
const line = {
|
|
@@ -2469,11 +2575,11 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
2469
2575
|
},
|
|
2470
2576
|
text: [
|
|
2471
2577
|
several
|
|
2472
|
-
? `${booked.length} bookings, one for each stay - each hotel
|
|
2578
|
+
? `${booked.length} bookings, one for each stay - each hotel keeps its own nights under its own code, and each is ${(deps.backend.capabilities ?? FULL_CAPABILITIES).changes ? 'changed or cancelled' : 'cancelled'} on its own.`
|
|
2473
2579
|
: '',
|
|
2474
2580
|
...booked.flatMap(({ booking, asked }) => [
|
|
2475
2581
|
`**${booking.confirmationCode}** · ${booking.hotelName} · ${booking.stay.checkIn} to ${booking.stay.checkOut}`,
|
|
2476
|
-
|
|
2582
|
+
bookedLine(deps, booking),
|
|
2477
2583
|
...asked,
|
|
2478
2584
|
]),
|
|
2479
2585
|
remembered?.line ?? '',
|
|
@@ -2737,18 +2843,23 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
2737
2843
|
const cancelled = await deps.backend.cancelBooking(ref, who, str(args.reason) || undefined);
|
|
2738
2844
|
if (!cancelled)
|
|
2739
2845
|
return asPreview;
|
|
2846
|
+
// ⚠️ WHAT THE HOTEL SAYS IT CHARGED, once it has said. A group's own system
|
|
2847
|
+
// answers a cancellation with its penalty; the preview was our reading of
|
|
2848
|
+
// the rate's terms, and where the two differ the hotel's is the one that
|
|
2849
|
+
// will be charged.
|
|
2850
|
+
const charged = cancelled.cancellationFee ?? preview.fee;
|
|
2740
2851
|
return {
|
|
2741
2852
|
structured: {
|
|
2742
2853
|
view: 'cancelled',
|
|
2743
2854
|
...common(deps, key),
|
|
2744
2855
|
booking: { ...cancelled, quote: quoteForCard(cancelled.quote) },
|
|
2745
|
-
fee: formatMoney(
|
|
2856
|
+
fee: formatMoney(charged),
|
|
2746
2857
|
refund: formatMoney(preview.refund),
|
|
2747
2858
|
rule: preview.rule,
|
|
2748
2859
|
cancellable: false,
|
|
2749
2860
|
},
|
|
2750
2861
|
text: [
|
|
2751
|
-
`${cancelled.confirmationCode} is cancelled. ${
|
|
2862
|
+
`${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `The hotel charges ${formatMoney(charged)}.`}`,
|
|
2752
2863
|
deps.vocab.demo ? DEMO_NOTE : '',
|
|
2753
2864
|
]
|
|
2754
2865
|
.filter(Boolean)
|