@ticketboothapp/booking 1.2.166 → 1.2.168
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/package.json +1 -1
- package/src/components/booking/AdminChangeBookingContent.tsx +15 -1
- package/src/components/booking/AdminChangeBookingFlow.tsx +274 -94
- package/src/components/booking/AdminChangeCheckoutPanel.tsx +49 -13
- package/src/components/booking/AdminChangePricingPanel.tsx +400 -134
- package/src/components/booking/AdminChangePromoEditor.tsx +97 -0
- package/src/components/booking/AdminChangeReceiptComparison.tsx +172 -147
- package/src/components/booking/ChangeBookingFlow.tsx +6 -3
- package/src/components/booking/ChangeBookingQuoteStatusPlaceholder.tsx +7 -2
- package/src/components/booking/ChangeBookingSelectionControlsPanel.tsx +6 -0
- package/src/components/booking/ChangeBookingTicketsAndAddOnsPanel.tsx +80 -0
- package/src/components/booking/NewBookingFlow.tsx +14 -2
- package/src/components/booking/PickupLocationSelector.module.css +22 -0
- package/src/components/booking/PickupLocationSelector.tsx +3 -3
- package/src/components/booking/PriceBreakdown.tsx +66 -15
- package/src/components/booking/PriceSummary.tsx +16 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +52 -14
- package/src/components/booking/PrivateShuttleCheckoutSection.tsx +95 -54
- package/src/components/booking/admin-adjustment-components.ts +44 -0
- package/src/components/booking/admin-adjustment-tax-behavior.ts +50 -0
- package/src/components/booking/admin-change-flow-state-helpers.ts +10 -0
- package/src/components/booking/admin-change-provider-payload.ts +12 -1
- package/src/components/booking/admin-change-quote-request-key.ts +41 -1
- package/src/components/booking/admin-change-receipt-lines.ts +49 -0
- package/src/components/booking/admin-change-v2-quote-request.ts +109 -0
- package/src/components/booking/admin-refund-decision-context.ts +122 -0
- package/src/components/booking/admin-refund-disposition.ts +71 -2
- package/src/components/booking/availability-date-selection.ts +16 -0
- package/src/components/booking/booking-flow-types.ts +4 -0
- package/src/components/booking/booking-flow.css +5 -0
- package/src/components/booking/change-booking-error-message.ts +137 -0
- package/src/components/booking/change-booking-flow-helpers.ts +77 -2
- package/src/components/booking/change-booking-quote-guards.ts +4 -1
- package/src/components/booking/change-booking-quote-state.ts +44 -1
- package/src/components/booking/incompatible-add-on-selections.ts +19 -0
- package/src/components/booking/private-shuttle-availability.ts +14 -0
- package/src/components/booking/private-shuttle-cancellation-policy.ts +9 -0
- package/src/components/booking/private-shuttle-checkout-controller.ts +5 -0
- package/src/components/booking/private-shuttle-provider-change-runner.ts +21 -0
- package/src/components/booking/private-shuttle-reservation-runner.ts +5 -3
- package/src/components/booking/provider-dashboard-change-booking.ts +18 -2
- package/src/components/booking/use-standard-booking-availability.ts +5 -2
- package/src/components/booking/useAdminChangeCheckoutController.ts +56 -1
- package/src/components/booking/useAdminChangeProductReset.ts +6 -0
- package/src/components/booking/useAdminChangeProtectedPricing.ts +4 -0
- package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +8 -6
- package/src/components/booking/useAdminChangeQuotePreview.ts +146 -61
- package/src/components/booking/useAdminCustomReceiptLines.ts +171 -10
- package/src/components/booking/useAdminProviderPricingAdjustments.ts +50 -19
- package/src/components/booking/useBookingAvailabilityAddOns.ts +26 -4
- package/src/components/booking/useBookingPromoAndQuantityController.ts +16 -2
- package/src/components/booking/useChangeBookingAddOnFloorController.ts +15 -9
- package/src/components/booking/useChangeBookingAutoSelections.ts +60 -17
- package/src/components/booking/useChangeBookingInitialHydration.ts +10 -7
- package/src/components/booking/useChangeBookingQuotePreview.ts +3 -1
- package/src/components/booking/useChangeBookingSelectionDetails.ts +23 -17
- package/src/components/booking/useStandardBookingAutoSelections.ts +55 -7
- package/src/lib/booking/booking-cutoffs.ts +22 -0
- package/src/lib/booking/change-booking-server-preview.ts +92 -5
- package/src/lib/booking/change-flow-pricing.ts +12 -0
- package/src/lib/booking/i18n/messages/en.json +1 -0
- package/src/lib/booking/i18n/messages/fr.json +1 -0
- package/src/lib/booking-api.ts +76 -1
- package/test/change-booking-helpers.test.ts +1319 -7
- package/src/components/booking/useAdminChangePricingDebugPanel.tsx +0 -178
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
type AdminAmendmentRefundDisposition,
|
|
6
|
+
type AdminPromoApplicationScope,
|
|
7
|
+
type AddOn,
|
|
6
8
|
type Availability,
|
|
7
9
|
type ReturnOption,
|
|
10
|
+
getAddOns,
|
|
8
11
|
} from '../../lib/booking-api';
|
|
9
12
|
import {
|
|
10
13
|
AdminChangeBookingContent,
|
|
@@ -15,15 +18,18 @@ import { CURRENCIES, DEFAULT_CURRENCY, type Currency } from './CurrencySwitcher'
|
|
|
15
18
|
import { useCompanyTimezone } from '../../contexts/CompanyContext';
|
|
16
19
|
import { useBookingApp } from '../../contexts/BookingAppContext';
|
|
17
20
|
import { useBookingHost } from '../../runtime';
|
|
21
|
+
import { filterAvailabilitiesAfterCurrentTime } from '../../lib/booking/booking-cutoffs';
|
|
18
22
|
import type { ChangeBookingFlowProps } from './booking-flow-types';
|
|
19
23
|
import {
|
|
20
24
|
normalizeAddOnSelections,
|
|
25
|
+
resolveChangeBookingCancellationPolicyId,
|
|
21
26
|
} from './change-booking-flow-helpers';
|
|
22
27
|
import {
|
|
23
28
|
buildChangeBookingMinimumQuantities,
|
|
24
29
|
isAdminManualOverrideEligibleLine,
|
|
25
30
|
resolveChangeFlowOriginalDate,
|
|
26
31
|
resolveInitialChangeProductOptionId,
|
|
32
|
+
shouldLockExistingAddOnQuantities,
|
|
27
33
|
} from './admin-change-flow-state-helpers';
|
|
28
34
|
import { useAdminChangeProducts } from './useAdminChangeProducts';
|
|
29
35
|
import {
|
|
@@ -40,6 +46,8 @@ import { useAdminChangePriceSummary } from './useAdminChangePriceSummary';
|
|
|
40
46
|
import { useAdminChangeReceiptDerivation } from './useAdminChangeReceiptDerivation';
|
|
41
47
|
import { useAdminChangeQuotePreview } from './useAdminChangeQuotePreview';
|
|
42
48
|
import { useBookingAvailabilityAddOns } from './useBookingAvailabilityAddOns';
|
|
49
|
+
import { incompatibleAddOnSelections } from './incompatible-add-on-selections';
|
|
50
|
+
import { incompatibleAddOnChangeError } from './change-booking-error-message';
|
|
43
51
|
import { useBookingPromoAndQuantityController } from './useBookingPromoAndQuantityController';
|
|
44
52
|
import { useChangeBookingItineraryUiState } from './useChangeBookingItineraryUiState';
|
|
45
53
|
import { useChangeBookingAutoSelections } from './useChangeBookingAutoSelections';
|
|
@@ -48,22 +56,22 @@ import { useChangeBookingAddOnFloorController } from './useChangeBookingAddOnFlo
|
|
|
48
56
|
import { useChangeBookingReceiptFloorController } from './useChangeBookingReceiptFloorController';
|
|
49
57
|
import { useAdminCustomReceiptLines } from './useAdminCustomReceiptLines';
|
|
50
58
|
import { useAdminChangeQuoteDisplayState } from './useAdminChangeQuoteDisplayState';
|
|
51
|
-
import { useAdminChangePricingDebugPanel } from './useAdminChangePricingDebugPanel';
|
|
52
59
|
import { useAdminChangeOrderSummaryModel } from './useAdminChangeOrderSummaryModel';
|
|
53
60
|
import { useActiveProductOptions } from './useActiveProductOptions';
|
|
54
61
|
import { useSeedPromoCodeInput } from './useSeedPromoCodeInput';
|
|
55
62
|
import { useAdminChangeProductReset } from './useAdminChangeProductReset';
|
|
56
63
|
import { useBookingQuantityCapTrim } from './useBookingQuantityCapTrim';
|
|
57
64
|
import { useBookingViewItemAnalytics } from './useBookingViewItemAnalytics';
|
|
65
|
+
import { AdminChangePromoEditor } from './AdminChangePromoEditor';
|
|
58
66
|
import { buildAdminChangeQuoteRequestKey } from './admin-change-quote-request-key';
|
|
59
67
|
import type { AdminReleaseRefundDisposition } from './admin-refund-disposition';
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
};
|
|
68
|
+
import {
|
|
69
|
+
defaultKeepOriginalReturnPriceDispositions,
|
|
70
|
+
defaultPendingRefundDispositions,
|
|
71
|
+
mergeAdminRefundDecisionContext,
|
|
72
|
+
selectRefundDispositionsForOperations,
|
|
73
|
+
type AdminRefundDecisionContext,
|
|
74
|
+
} from './admin-refund-decision-context';
|
|
67
75
|
|
|
68
76
|
/**
|
|
69
77
|
* ## Pricing contract (customer self-serve)
|
|
@@ -148,6 +156,24 @@ export function AdminChangeBookingFlow({
|
|
|
148
156
|
isAdmin,
|
|
149
157
|
companyId: env.COMPANY_ID,
|
|
150
158
|
});
|
|
159
|
+
const [adminAvailabilityNowMs, setAdminAvailabilityNowMs] = useState(() => Date.now());
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const interval = window.setInterval(() => setAdminAvailabilityNowMs(Date.now()), 60 * 1000);
|
|
163
|
+
return () => window.clearInterval(interval);
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
const adminAvailabilityNow = useMemo(
|
|
167
|
+
() => new Date(adminAvailabilityNowMs),
|
|
168
|
+
[adminAvailabilityNowMs],
|
|
169
|
+
);
|
|
170
|
+
const filterAdminCalendarAvailabilities = useCallback(
|
|
171
|
+
(rows: Availability[]) =>
|
|
172
|
+
product.productType === 'PRIVATE_SHUTTLE'
|
|
173
|
+
? rows
|
|
174
|
+
: filterAvailabilitiesAfterCurrentTime(rows, adminAvailabilityNow),
|
|
175
|
+
[adminAvailabilityNow, product.productType],
|
|
176
|
+
);
|
|
151
177
|
const [selectedAvailability, setSelectedAvailability] = useState<Availability | null>(null);
|
|
152
178
|
const [selectedReturnOption, setSelectedReturnOption] = useState<ReturnOption | null>(null);
|
|
153
179
|
const [quantities, setQuantities] = useState<Record<string, number>>({});
|
|
@@ -156,29 +182,83 @@ export function AdminChangeBookingFlow({
|
|
|
156
182
|
const [lastName, setLastName] = useState('');
|
|
157
183
|
const [promoCodeInput, setPromoCodeInput] = useState('');
|
|
158
184
|
const [appliedPromoCode, setAppliedPromoCode] = useState<string | null>(null);
|
|
185
|
+
const [promoApplicationScope, setPromoApplicationScope] =
|
|
186
|
+
useState<AdminPromoApplicationScope>('CHANGE_ONLY');
|
|
159
187
|
const [pickupLocationId, setPickupLocationId] = useState<string | null>(null);
|
|
160
188
|
const [pickupLocationSkipped, setPickupLocationSkipped] = useState(false);
|
|
161
|
-
/**
|
|
162
|
-
const cancellationPolicyId =
|
|
189
|
+
/** The policy remains non-editable, but a family conversion must adopt the target default. */
|
|
190
|
+
const [cancellationPolicyId, setCancellationPolicyId] = useState<string | null>(
|
|
163
191
|
() => initialValues?.cancellationPolicyId?.trim() ?? null,
|
|
164
|
-
[initialValues?.cancellationPolicyId],
|
|
165
192
|
);
|
|
166
193
|
/** Add-on selections (lunch, animals, etc.) - filtered by selected product option */
|
|
167
194
|
const [addOnSelections, setAddOnSelections] = useState<Array<{ addOnId: string; variantId?: string; quantity?: number }>>(() =>
|
|
168
195
|
normalizeAddOnSelections(initialValues?.addOnSelections ?? [])
|
|
169
196
|
);
|
|
170
197
|
/** Fetched add-ons for the selected product option */
|
|
171
|
-
const {
|
|
198
|
+
const {
|
|
199
|
+
addOns,
|
|
200
|
+
setAddOns,
|
|
201
|
+
availabilityProductOptionId,
|
|
202
|
+
loadedProductOptionId,
|
|
203
|
+
} = useBookingAvailabilityAddOns({
|
|
172
204
|
selectedAvailability,
|
|
173
205
|
companyId: product.companyId,
|
|
174
206
|
});
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
207
|
+
const [knownAddOns, setKnownAddOns] = useState<AddOn[]>([]);
|
|
208
|
+
const mergeKnownAddOns = useCallback((rows: AddOn[]) => {
|
|
209
|
+
if (rows.length === 0) return;
|
|
210
|
+
setKnownAddOns((current) => {
|
|
211
|
+
const merged = new Map(current.map((addOn) => [addOn.addOnId, addOn]));
|
|
212
|
+
let changed = false;
|
|
213
|
+
for (const addOn of rows) {
|
|
214
|
+
if (merged.get(addOn.addOnId) !== addOn) {
|
|
215
|
+
merged.set(addOn.addOnId, addOn);
|
|
216
|
+
changed = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return changed ? Array.from(merged.values()) : current;
|
|
220
|
+
});
|
|
221
|
+
}, []);
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
mergeKnownAddOns(addOns);
|
|
224
|
+
}, [addOns, mergeKnownAddOns]);
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
let cancelled = false;
|
|
227
|
+
if (!product.companyId) return;
|
|
228
|
+
getAddOns(product.companyId, {
|
|
229
|
+
preCheckout: true,
|
|
230
|
+
dateTime: initialValues?.dateTime?.trim() || undefined,
|
|
231
|
+
})
|
|
232
|
+
.then((rows) => {
|
|
233
|
+
if (!cancelled) mergeKnownAddOns(rows);
|
|
234
|
+
})
|
|
235
|
+
.catch(() => {
|
|
236
|
+
// The selected tour's catalog still renders; the warning keeps a generic fallback.
|
|
237
|
+
});
|
|
238
|
+
return () => {
|
|
239
|
+
cancelled = true;
|
|
240
|
+
};
|
|
241
|
+
}, [product.companyId, initialValues?.dateTime, mergeKnownAddOns]);
|
|
242
|
+
const addOnCatalogLoaded =
|
|
243
|
+
availabilityProductOptionId != null &&
|
|
244
|
+
loadedProductOptionId === availabilityProductOptionId;
|
|
245
|
+
const incompatibleSelections = incompatibleAddOnSelections(
|
|
246
|
+
addOnSelections,
|
|
247
|
+
addOns,
|
|
248
|
+
addOnCatalogLoaded,
|
|
249
|
+
);
|
|
250
|
+
const incompatibleAddOnIds = Array.from(
|
|
251
|
+
new Set(incompatibleSelections.map((selection) => selection.addOnId)),
|
|
252
|
+
);
|
|
253
|
+
const hasIncompatibleAddOnSelections = incompatibleAddOnIds.length > 0;
|
|
254
|
+
const incompatibleAddOnError = hasIncompatibleAddOnSelections
|
|
255
|
+
? incompatibleAddOnChangeError(
|
|
256
|
+
incompatibleAddOnIds.map((addOnId) => (
|
|
257
|
+
knownAddOns.find((addOn) => addOn.addOnId === addOnId)?.name ?? 'Selected add-on'
|
|
258
|
+
)),
|
|
259
|
+
product.name,
|
|
260
|
+
)
|
|
261
|
+
: '';
|
|
182
262
|
useEffect(() => {
|
|
183
263
|
if (!pickupLocationId) return;
|
|
184
264
|
const pickupLocations = product.pickupLocations ?? [];
|
|
@@ -191,7 +271,7 @@ export function AdminChangeBookingFlow({
|
|
|
191
271
|
useSeedPromoCodeInput(autoAppliedPromoCode, setPromoCodeInput);
|
|
192
272
|
const [loading, setLoading] = useState(false);
|
|
193
273
|
const [error, setError] = useState('');
|
|
194
|
-
const [termsAccepted, setTermsAccepted] = useState(
|
|
274
|
+
const [termsAccepted, setTermsAccepted] = useState(true);
|
|
195
275
|
const [termsAcceptedAt, setTermsAcceptedAt] = useState<string | null>(null);
|
|
196
276
|
const [partnerAttributionConfirmed, setPartnerAttributionConfirmed] = useState(false);
|
|
197
277
|
/** Admin only: skip sending confirmation at creation (provider dashboard). */
|
|
@@ -224,10 +304,11 @@ export function AdminChangeBookingFlow({
|
|
|
224
304
|
* user picks a different return time — baseline is the first auto-selected return for this outbound.
|
|
225
305
|
*/
|
|
226
306
|
const [implicitReturnBaselineId, setImplicitReturnBaselineId] = useState<string | null>(null);
|
|
227
|
-
|
|
228
|
-
const lockedPromoCode = initialValues?.promoCode?.trim()
|
|
307
|
+
const historicalPromoCode = initialValues?.promoCode?.trim()
|
|
229
308
|
? initialValues.promoCode.trim().toUpperCase()
|
|
230
309
|
: null;
|
|
310
|
+
/** Public/self-serve keeps its historical promo locked. Admin amendments require new explicit intent. */
|
|
311
|
+
const lockedPromoCode = isProviderDashboardChange ? null : historicalPromoCode;
|
|
231
312
|
/** Public self-serve only: cannot reduce tickets below original counts. Provider-dashboard admins may reduce party size. */
|
|
232
313
|
const changeBookingMinimumQuantities = useMemo(
|
|
233
314
|
() =>
|
|
@@ -239,6 +320,21 @@ export function AdminChangeBookingFlow({
|
|
|
239
320
|
[isCustomerSelfServeChange, initialValues?.bookingItems, isAdmin],
|
|
240
321
|
);
|
|
241
322
|
const [latestChangeQuote, setLatestChangeQuote] = useState<AdminChangeLatestQuote | null>(null);
|
|
323
|
+
const {
|
|
324
|
+
adminCustomReceiptLines,
|
|
325
|
+
appliedAdminCustomReceiptLines,
|
|
326
|
+
adminReverseAdjustmentIds,
|
|
327
|
+
hasPendingAdminCustomReceiptLineChanges,
|
|
328
|
+
pendingAdminCustomReceiptLineIds,
|
|
329
|
+
hasHydratedExistingAdjustments,
|
|
330
|
+
handleAddAdminCustomReceiptLine,
|
|
331
|
+
handleUpdateAdminCustomReceiptLine,
|
|
332
|
+
handleRemoveAdminCustomReceiptLine,
|
|
333
|
+
handleApplyAdminCustomReceiptLines: handleApplyAdminCustomReceiptLineDrafts,
|
|
334
|
+
} = useAdminCustomReceiptLines(
|
|
335
|
+
selectedChangeProductId,
|
|
336
|
+
latestChangeQuote?.reversibleAdjustments,
|
|
337
|
+
);
|
|
242
338
|
const [refundDecisionContext, setRefundDecisionContext] = useState<AdminRefundDecisionContext | null>(null);
|
|
243
339
|
const [refundDispositionsByOperationId, setRefundDispositionsByOperationId] = useState<
|
|
244
340
|
Record<string, AdminAmendmentRefundDisposition>
|
|
@@ -256,6 +352,12 @@ export function AdminChangeBookingFlow({
|
|
|
256
352
|
(latestChangeQuote.refundDecisionOperations?.length ?? 0) > 0) &&
|
|
257
353
|
latestChangeQuote.serverDisplay != null;
|
|
258
354
|
const changeQuoteRequestSeq = useRef(0);
|
|
355
|
+
const handleApplyAdminCustomReceiptLines = useCallback(() => {
|
|
356
|
+
setLatestChangeQuote(null);
|
|
357
|
+
setChangeQuoteFetchError(null);
|
|
358
|
+
setChangeQuoteLoading(true);
|
|
359
|
+
handleApplyAdminCustomReceiptLineDrafts();
|
|
360
|
+
}, [handleApplyAdminCustomReceiptLineDrafts]);
|
|
259
361
|
const { activeOptions, activeOptionIdsKey, optionsMap } = useActiveProductOptions(product.options);
|
|
260
362
|
|
|
261
363
|
const {
|
|
@@ -283,13 +385,37 @@ export function AdminChangeBookingFlow({
|
|
|
283
385
|
appliedPromoCode,
|
|
284
386
|
pricingProfileIdForAvailabilities,
|
|
285
387
|
cancellationPolicyProfileIdForAvailabilities,
|
|
388
|
+
filterCalendarAvailabilities: filterAdminCalendarAvailabilities,
|
|
286
389
|
resetKey: selectedChangeProductId,
|
|
287
390
|
setQuantities,
|
|
288
391
|
setError,
|
|
289
392
|
});
|
|
290
393
|
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
const policies = pricingConfig?.cancellationPolicies;
|
|
396
|
+
if (!policies?.length) return;
|
|
397
|
+
const isProductFamilyConversion =
|
|
398
|
+
isInitialPrivateShuttleBooking !== (product.productType === 'PRIVATE_SHUTTLE');
|
|
399
|
+
const resolvedPolicyId = resolveChangeBookingCancellationPolicyId(
|
|
400
|
+
cancellationPolicyId,
|
|
401
|
+
policies,
|
|
402
|
+
currency,
|
|
403
|
+
isProductFamilyConversion,
|
|
404
|
+
);
|
|
405
|
+
if (resolvedPolicyId !== cancellationPolicyId) {
|
|
406
|
+
setCancellationPolicyId(resolvedPolicyId);
|
|
407
|
+
}
|
|
408
|
+
}, [
|
|
409
|
+
cancellationPolicyId,
|
|
410
|
+
currency,
|
|
411
|
+
isInitialPrivateShuttleBooking,
|
|
412
|
+
pricingConfig?.cancellationPolicies,
|
|
413
|
+
product.productType,
|
|
414
|
+
]);
|
|
415
|
+
|
|
291
416
|
useAdminChangeProductReset({
|
|
292
417
|
selectedChangeProductId,
|
|
418
|
+
setQuantities,
|
|
293
419
|
setSelectedAvailability,
|
|
294
420
|
setSelectedReturnOption,
|
|
295
421
|
setAddOns,
|
|
@@ -353,6 +479,8 @@ export function AdminChangeBookingFlow({
|
|
|
353
479
|
timesForSelectedDate,
|
|
354
480
|
companyTimezone,
|
|
355
481
|
changeFlowResolvedInitialProductOptionId,
|
|
482
|
+
mostPopularProductOptionId:
|
|
483
|
+
activeOptions.find((option) => option.mostPopular)?.optionId ?? null,
|
|
356
484
|
precomputedPricesByOption,
|
|
357
485
|
currency,
|
|
358
486
|
originalReceiptSubtotal: originalReceipt?.subtotal,
|
|
@@ -373,7 +501,12 @@ export function AdminChangeBookingFlow({
|
|
|
373
501
|
setError,
|
|
374
502
|
});
|
|
375
503
|
|
|
504
|
+
const lockExistingAddOnQuantities = shouldLockExistingAddOnQuantities({
|
|
505
|
+
isCustomerSelfServeChange,
|
|
506
|
+
isAdmin,
|
|
507
|
+
});
|
|
376
508
|
const {
|
|
509
|
+
initialAddOnQtyByKey,
|
|
377
510
|
initialAddOnMinQtyByKey,
|
|
378
511
|
initialAddOnMinTotalByAddOnId,
|
|
379
512
|
updateAddOnSelections,
|
|
@@ -381,7 +514,7 @@ export function AdminChangeBookingFlow({
|
|
|
381
514
|
initialValues,
|
|
382
515
|
originalReceiptLineItems: originalReceipt?.lineItems,
|
|
383
516
|
addOns,
|
|
384
|
-
|
|
517
|
+
lockExistingAddOnQuantities,
|
|
385
518
|
setAddOnSelections,
|
|
386
519
|
});
|
|
387
520
|
|
|
@@ -507,7 +640,6 @@ export function AdminChangeBookingFlow({
|
|
|
507
640
|
editableSummaryLineAmountInputs,
|
|
508
641
|
editableSummaryLineLabelInputs,
|
|
509
642
|
editableSummaryPreSubtotalDelta,
|
|
510
|
-
editableSummaryPreSubtotalDebugRows,
|
|
511
643
|
handleEditableLineAmountInputChange,
|
|
512
644
|
handleEditableLineAmountInputBlur,
|
|
513
645
|
handleEditableLineAmountReset,
|
|
@@ -535,7 +667,7 @@ export function AdminChangeBookingFlow({
|
|
|
535
667
|
originalAddOnSelections: initialValues?.addOnSelections ?? null,
|
|
536
668
|
originalProductOptionId: initialValues?.productOptionId ?? null,
|
|
537
669
|
addOns,
|
|
538
|
-
adminCustomReceiptLines,
|
|
670
|
+
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
539
671
|
quantities,
|
|
540
672
|
flowUi,
|
|
541
673
|
isProviderDashboardChange,
|
|
@@ -573,8 +705,6 @@ export function AdminChangeBookingFlow({
|
|
|
573
705
|
unchangedReceiptAnchor,
|
|
574
706
|
showChangeFlowManualPriceLines,
|
|
575
707
|
showAdminCustomLineEditor,
|
|
576
|
-
displayedChangeAmountsRaw,
|
|
577
|
-
displayLayerUsesExternalPricing,
|
|
578
708
|
displayChangeFlowTax,
|
|
579
709
|
displayChangeFlowProposedTotal,
|
|
580
710
|
displayChangeFlowProposedTotalWithEditableLines,
|
|
@@ -604,7 +734,7 @@ export function AdminChangeBookingFlow({
|
|
|
604
734
|
pickupLocationId,
|
|
605
735
|
quantities,
|
|
606
736
|
addOnSelections,
|
|
607
|
-
|
|
737
|
+
initialAddOnQtyByKey,
|
|
608
738
|
changeFlowResolvedInitialProductOptionId,
|
|
609
739
|
selectedParentProductChanged,
|
|
610
740
|
isProviderDashboardChange,
|
|
@@ -613,7 +743,8 @@ export function AdminChangeBookingFlow({
|
|
|
613
743
|
providerQuotedLines,
|
|
614
744
|
editableSummaryPreSubtotalDelta,
|
|
615
745
|
adminCustomAdjustmentTotal,
|
|
616
|
-
adminCustomReceiptLines,
|
|
746
|
+
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
747
|
+
adminReverseAdjustmentIds,
|
|
617
748
|
isManualOverrideEligibleLine,
|
|
618
749
|
isAdmin,
|
|
619
750
|
useAdminFeAuthoritativeQuote,
|
|
@@ -651,37 +782,6 @@ export function AdminChangeBookingFlow({
|
|
|
651
782
|
editableSummaryPreSubtotalDelta,
|
|
652
783
|
});
|
|
653
784
|
|
|
654
|
-
const changeFlowAdminPricingDebugPanel = useAdminChangePricingDebugPanel({
|
|
655
|
-
isAdmin,
|
|
656
|
-
hasSelectedAvailability: selectedAvailability != null,
|
|
657
|
-
totalQuantity,
|
|
658
|
-
useAdminFeAuthoritativeQuote,
|
|
659
|
-
currency,
|
|
660
|
-
locale,
|
|
661
|
-
originalReceipt,
|
|
662
|
-
isCustomerSelfServeChange,
|
|
663
|
-
latestChangeQuote,
|
|
664
|
-
changeQuoteFetchError,
|
|
665
|
-
displayLayerUsesExternalPricing,
|
|
666
|
-
selfServePricingConfirmed,
|
|
667
|
-
changeQuoteLoading,
|
|
668
|
-
effectiveSubtotal,
|
|
669
|
-
adminCustomAdjustmentTotal,
|
|
670
|
-
editableSummaryPreSubtotalDebugRows,
|
|
671
|
-
effectiveSubtotalForCheckout,
|
|
672
|
-
effectiveTax,
|
|
673
|
-
effectivePromoDiscountAmount,
|
|
674
|
-
totalPrice,
|
|
675
|
-
changeFlowNewBookingTotal,
|
|
676
|
-
displayedChangeAmountsRaw,
|
|
677
|
-
editableSummaryPreSubtotalDelta,
|
|
678
|
-
displayChangeFlowProposedTotal,
|
|
679
|
-
displayChangeFlowProposedTotalWithEditableLines,
|
|
680
|
-
adminFeAuthoritativeReceipt,
|
|
681
|
-
changeFlowClientEstimateDue,
|
|
682
|
-
changeFlowAmountDue,
|
|
683
|
-
});
|
|
684
|
-
|
|
685
785
|
const refundDecisionSelectionKey = useMemo(() => buildAdminChangeQuoteRequestKey({
|
|
686
786
|
bookingReference: initialValues?.bookingReference,
|
|
687
787
|
lastName,
|
|
@@ -692,8 +792,11 @@ export function AdminChangeBookingFlow({
|
|
|
692
792
|
quantities,
|
|
693
793
|
addOnSelections,
|
|
694
794
|
cancellationPolicyId,
|
|
695
|
-
|
|
795
|
+
promoCode: appliedPromoCode,
|
|
796
|
+
promoApplicationScope,
|
|
797
|
+
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
696
798
|
structuredAdjustments: adminStructuredAdjustments,
|
|
799
|
+
reverseAdjustmentIds: adminReverseAdjustmentIds,
|
|
697
800
|
useAdminFeAuthoritativeQuote,
|
|
698
801
|
}), [
|
|
699
802
|
initialValues?.bookingReference,
|
|
@@ -705,8 +808,11 @@ export function AdminChangeBookingFlow({
|
|
|
705
808
|
quantities,
|
|
706
809
|
addOnSelections,
|
|
707
810
|
cancellationPolicyId,
|
|
708
|
-
|
|
811
|
+
appliedPromoCode,
|
|
812
|
+
promoApplicationScope,
|
|
813
|
+
appliedAdminCustomReceiptLines,
|
|
709
814
|
adminStructuredAdjustments,
|
|
815
|
+
adminReverseAdjustmentIds,
|
|
710
816
|
useAdminFeAuthoritativeQuote,
|
|
711
817
|
]);
|
|
712
818
|
const responseRefundDecisionOperations = latestChangeQuote?.refundDecisionOperations ?? [];
|
|
@@ -714,41 +820,51 @@ export function AdminChangeBookingFlow({
|
|
|
714
820
|
latestChangeQuote?.returnPriceTreatmentOperations ?? [];
|
|
715
821
|
useEffect(() => {
|
|
716
822
|
if (responseRefundDecisionOperations.length === 0) return;
|
|
717
|
-
setRefundDecisionContext({
|
|
823
|
+
setRefundDecisionContext((current) => mergeAdminRefundDecisionContext({
|
|
824
|
+
current,
|
|
718
825
|
selectionKey: refundDecisionSelectionKey,
|
|
719
826
|
operations: responseRefundDecisionOperations,
|
|
720
827
|
allowedByOperationId: latestChangeQuote?.allowedRefundDispositionsByOperationId ?? {},
|
|
721
828
|
removedValueByOperationId: latestChangeQuote?.removedValueByOperationId ?? {},
|
|
722
|
-
});
|
|
829
|
+
}));
|
|
723
830
|
}, [
|
|
724
831
|
refundDecisionSelectionKey,
|
|
725
832
|
responseRefundDecisionOperations,
|
|
726
833
|
latestChangeQuote?.allowedRefundDispositionsByOperationId,
|
|
727
834
|
latestChangeQuote?.removedValueByOperationId,
|
|
728
835
|
]);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
: refundDecisionContext
|
|
737
|
-
|
|
738
|
-
|
|
836
|
+
useEffect(() => {
|
|
837
|
+
if (!isProviderDashboardChange || responseRefundDecisionOperations.length === 0) return;
|
|
838
|
+
setRefundDispositionsByOperationId((current) =>
|
|
839
|
+
defaultPendingRefundDispositions(responseRefundDecisionOperations, current),
|
|
840
|
+
);
|
|
841
|
+
}, [isProviderDashboardChange, responseRefundDecisionOperations]);
|
|
842
|
+
const activeRefundDecisionContext = useMemo(() => mergeAdminRefundDecisionContext({
|
|
843
|
+
current: refundDecisionContext,
|
|
844
|
+
selectionKey: refundDecisionSelectionKey,
|
|
845
|
+
operations: responseRefundDecisionOperations,
|
|
846
|
+
allowedByOperationId: latestChangeQuote?.allowedRefundDispositionsByOperationId ?? {},
|
|
847
|
+
removedValueByOperationId: latestChangeQuote?.removedValueByOperationId ?? {},
|
|
848
|
+
}), [
|
|
849
|
+
refundDecisionContext,
|
|
850
|
+
refundDecisionSelectionKey,
|
|
851
|
+
responseRefundDecisionOperations,
|
|
852
|
+
latestChangeQuote?.allowedRefundDispositionsByOperationId,
|
|
853
|
+
latestChangeQuote?.removedValueByOperationId,
|
|
854
|
+
]);
|
|
739
855
|
const activeReturnPriceTreatmentOperations =
|
|
740
856
|
responseReturnPriceTreatmentOperations.length > 0
|
|
741
857
|
? responseReturnPriceTreatmentOperations
|
|
742
858
|
: [];
|
|
743
859
|
const activeRefundDispositionsByOperationId = useMemo(() => {
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
860
|
+
const dispositionsWithReturnPriceDefaults = defaultKeepOriginalReturnPriceDispositions(
|
|
861
|
+
activeReturnPriceTreatmentOperations,
|
|
862
|
+
refundDispositionsByOperationId,
|
|
863
|
+
);
|
|
864
|
+
return selectRefundDispositionsForOperations(
|
|
865
|
+
activeRefundDecisionContext,
|
|
866
|
+
activeReturnPriceTreatmentOperations,
|
|
867
|
+
dispositionsWithReturnPriceDefaults,
|
|
752
868
|
);
|
|
753
869
|
}, [
|
|
754
870
|
activeRefundDecisionContext?.operations,
|
|
@@ -787,8 +903,13 @@ export function AdminChangeBookingFlow({
|
|
|
787
903
|
quantities,
|
|
788
904
|
addOnSelections,
|
|
789
905
|
cancellationPolicyId,
|
|
790
|
-
|
|
906
|
+
appliedPromoCode,
|
|
907
|
+
promoApplicationScope,
|
|
908
|
+
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
791
909
|
adminStructuredAdjustments,
|
|
910
|
+
adminReverseAdjustmentIds,
|
|
911
|
+
needsAdjustmentDiscovery:
|
|
912
|
+
isProviderDashboardChange && !hasHydratedExistingAdjustments,
|
|
792
913
|
refundDispositionsByOperationId: activeRefundDispositionsByOperationId,
|
|
793
914
|
useAdminFeAuthoritativeQuote,
|
|
794
915
|
latestChangeQuote,
|
|
@@ -899,7 +1020,13 @@ export function AdminChangeBookingFlow({
|
|
|
899
1020
|
setPickupLocationSkipped,
|
|
900
1021
|
});
|
|
901
1022
|
|
|
902
|
-
const {
|
|
1023
|
+
const {
|
|
1024
|
+
handleQuantityChange,
|
|
1025
|
+
handleApplyPromo,
|
|
1026
|
+
handleRemovePromo,
|
|
1027
|
+
promoCodeError,
|
|
1028
|
+
promoCodeValidating,
|
|
1029
|
+
} = useBookingPromoAndQuantityController({
|
|
903
1030
|
isAdmin,
|
|
904
1031
|
effectivePartySizeCap,
|
|
905
1032
|
currentTotalQuantity: orderSummary.totalQuantity,
|
|
@@ -918,6 +1045,33 @@ export function AdminChangeBookingFlow({
|
|
|
918
1045
|
setError,
|
|
919
1046
|
});
|
|
920
1047
|
|
|
1048
|
+
const adminPromoEditor = (
|
|
1049
|
+
<AdminChangePromoEditor
|
|
1050
|
+
previousPromoCode={historicalPromoCode}
|
|
1051
|
+
promoCodeInput={promoCodeInput}
|
|
1052
|
+
appliedPromoCode={appliedPromoCode}
|
|
1053
|
+
promoCodeError={promoCodeError}
|
|
1054
|
+
promoCodeValidating={promoCodeValidating}
|
|
1055
|
+
scope={promoApplicationScope}
|
|
1056
|
+
currency={currency}
|
|
1057
|
+
locale={locale}
|
|
1058
|
+
t={t}
|
|
1059
|
+
onInputChange={(value) => {
|
|
1060
|
+
setPromoCodeInput(value);
|
|
1061
|
+
if (appliedPromoCode && value.trim().toUpperCase() !== appliedPromoCode) {
|
|
1062
|
+
setAppliedPromoCode(null);
|
|
1063
|
+
}
|
|
1064
|
+
}}
|
|
1065
|
+
onApply={() => { void handleApplyPromo(); }}
|
|
1066
|
+
onRemove={() => {
|
|
1067
|
+
setPromoCodeInput('');
|
|
1068
|
+
setPromoApplicationScope('CHANGE_ONLY');
|
|
1069
|
+
handleRemovePromo();
|
|
1070
|
+
}}
|
|
1071
|
+
onScopeChange={setPromoApplicationScope}
|
|
1072
|
+
/>
|
|
1073
|
+
);
|
|
1074
|
+
|
|
921
1075
|
const {
|
|
922
1076
|
showCheckoutModal,
|
|
923
1077
|
checkoutClientSecret,
|
|
@@ -969,10 +1123,12 @@ export function AdminChangeBookingFlow({
|
|
|
969
1123
|
onChangeBooking,
|
|
970
1124
|
cancellationPolicyId,
|
|
971
1125
|
appliedPromoCode,
|
|
1126
|
+
promoApplicationScope,
|
|
972
1127
|
displayChangeFlowProposedTotalWithEditableLines,
|
|
973
1128
|
providerPricingOverrides,
|
|
974
1129
|
mergedProviderAdditionalAdjustments,
|
|
975
1130
|
adminStructuredAdjustments,
|
|
1131
|
+
adminReverseAdjustmentIds,
|
|
976
1132
|
refundDispositionsByOperationId: activeRefundDispositionsByOperationId,
|
|
977
1133
|
providerApplyAuthoritativeReceipt,
|
|
978
1134
|
onSuccess,
|
|
@@ -987,7 +1143,7 @@ export function AdminChangeBookingFlow({
|
|
|
987
1143
|
effectiveCancellationPolicyLabel,
|
|
988
1144
|
addOns,
|
|
989
1145
|
feeLineItems,
|
|
990
|
-
adminCustomReceiptLines,
|
|
1146
|
+
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
991
1147
|
isTaxIncludedInPrice,
|
|
992
1148
|
originalReceiptPromoAdjustment,
|
|
993
1149
|
effectivePromoLineType,
|
|
@@ -1063,9 +1219,9 @@ export function AdminChangeBookingFlow({
|
|
|
1063
1219
|
isSimplifiedPricingView, onQuantityChange: handleQuantityChange,
|
|
1064
1220
|
minimumQuantities: changeBookingMinimumQuantities, applyReceiptPaidFloors: changeFlowApplyReceiptPaidFloors,
|
|
1065
1221
|
ticketUnitFloorByCategory: changeFlowTicketBookedUnitPriceByCategory,
|
|
1066
|
-
...{ addOns, addOnSelections },
|
|
1222
|
+
...{ addOns, knownAddOns, addOnCatalogLoaded, addOnSelections },
|
|
1067
1223
|
onAddOnSelectionsChange: updateAddOnSelections,
|
|
1068
|
-
minimumTotalByAddOnId:
|
|
1224
|
+
minimumTotalByAddOnId: lockExistingAddOnQuantities ? initialAddOnMinTotalByAddOnId : undefined,
|
|
1069
1225
|
priceSummaryLines: taxIncludedReconciledCheckoutPriceSummaryLines,
|
|
1070
1226
|
replacePriceSummary: hasChangeSelection ? selfServeCheckoutPlaceholder : undefined,
|
|
1071
1227
|
changeFlowAmountDue,
|
|
@@ -1077,13 +1233,23 @@ export function AdminChangeBookingFlow({
|
|
|
1077
1233
|
// normalized server preview, render it; do not discard valid data because an
|
|
1078
1234
|
// optional/versioned metadata string is missing or renamed.
|
|
1079
1235
|
serverAmendmentLines: latestChangeQuote?.serverPreview?.priceSummaryLines ?? null,
|
|
1236
|
+
serverResultingReceiptLines:
|
|
1237
|
+
latestChangeQuote?.serverPreview?.resultingReceiptLines ?? null,
|
|
1080
1238
|
serverAmountDue: latestChangeQuote?.serverPreview?.amountDue ?? null,
|
|
1081
1239
|
serverUpdatedTotal: latestChangeQuote?.serverPreview?.totalNewBooking ?? null,
|
|
1240
|
+
serverNetPaid: latestChangeQuote?.amountPreviouslyPaid ?? null,
|
|
1241
|
+
serverCurrentPaymentCredit: latestChangeQuote?.currentPaymentCreditTotal ?? 0,
|
|
1242
|
+
serverProjectedPaymentCredit: latestChangeQuote?.projectedPaymentCreditTotal ?? 0,
|
|
1243
|
+
serverRefundCandidate: latestChangeQuote?.refundCandidate ?? 0,
|
|
1082
1244
|
serverQuoteError:
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1245
|
+
hasIncompatibleAddOnSelections
|
|
1246
|
+
? incompatibleAddOnError
|
|
1247
|
+
: (
|
|
1248
|
+
changeQuoteFetchError ??
|
|
1249
|
+
(isChangeQuoteBlocked
|
|
1250
|
+
? (latestChangeQuote?.reasonIfBlocked ?? 'This change cannot be priced right now.')
|
|
1251
|
+
: null)
|
|
1252
|
+
),
|
|
1087
1253
|
refundDecisionOperations: activeRefundDecisionContext?.operations ?? [],
|
|
1088
1254
|
returnPriceTreatmentOperations: activeReturnPriceTreatmentOperations,
|
|
1089
1255
|
allowedRefundDispositionsByOperationId: {
|
|
@@ -1095,14 +1261,21 @@ export function AdminChangeBookingFlow({
|
|
|
1095
1261
|
refundDispositionsByOperationId: activeRefundDispositionsByOperationId,
|
|
1096
1262
|
onRefundDispositionChange: handleRefundDispositionChange,
|
|
1097
1263
|
refundQuoteLoading: changeQuoteLoading,
|
|
1264
|
+
sourceProductName: initialProduct.name,
|
|
1265
|
+
targetProductName: product.name,
|
|
1098
1266
|
...{ priceSummaryLinesIncludeTaxRow, isTaxIncludedInPrice, showProviderPricingInlineEditor },
|
|
1099
1267
|
taxRate: pricingConfig?.taxRate,
|
|
1100
1268
|
...{ providerPricingUi, providerQuotedLines },
|
|
1101
|
-
|
|
1269
|
+
promoEditor: adminPromoEditor,
|
|
1270
|
+
promoCode: appliedPromoCode,
|
|
1102
1271
|
...{ showChangeFlowManualPriceLines, showAdminCustomLineEditor, adminCustomReceiptLines },
|
|
1272
|
+
hasPendingAdminCustomReceiptLineChanges,
|
|
1273
|
+
pendingAdminCustomReceiptLineIds,
|
|
1274
|
+
adjustmentComponentOptions: latestChangeQuote?.selectableAdjustmentComponents ?? [],
|
|
1103
1275
|
onAddAdminCustomReceiptLine: handleAddAdminCustomReceiptLine,
|
|
1104
1276
|
onUpdateAdminCustomReceiptLine: handleUpdateAdminCustomReceiptLine,
|
|
1105
1277
|
onRemoveAdminCustomReceiptLine: handleRemoveAdminCustomReceiptLine,
|
|
1278
|
+
onApplyAdminCustomReceiptLines: handleApplyAdminCustomReceiptLines,
|
|
1106
1279
|
onFirstNameChange: setFirstName, onLastNameChange: setLastName, onEmailChange: setEmail,
|
|
1107
1280
|
onClearError: () => setError(''),
|
|
1108
1281
|
pickupLocations: product.pickupLocations, destinations: product.destinations, highlightedPickupLocationIds,
|
|
@@ -1112,11 +1285,18 @@ export function AdminChangeBookingFlow({
|
|
|
1112
1285
|
...{ skipConfirmationCommunications, disableAutoCommunications },
|
|
1113
1286
|
onSkipConfirmationChange: setSkipConfirmationCommunications,
|
|
1114
1287
|
onDisableCommunicationsChange: setDisableAutoCommunications,
|
|
1115
|
-
checkoutError: checkoutFormError,
|
|
1288
|
+
checkoutError: incompatibleAddOnError || checkoutFormError,
|
|
1116
1289
|
onCheckout: handleCheckout,
|
|
1117
1290
|
submitLabel: changeCheckoutButtonLabel ?? deferredInvoiceSubmitLabel,
|
|
1118
1291
|
...{ hasEffectiveChangeSelection, isChangeQuoteBlocked },
|
|
1119
|
-
submitDisabled:
|
|
1292
|
+
submitDisabled:
|
|
1293
|
+
changeFlowSubmitDisabled ||
|
|
1294
|
+
hasIncompatibleAddOnSelections ||
|
|
1295
|
+
hasPendingAdminCustomReceiptLineChanges ||
|
|
1296
|
+
adminCustomReceiptLines.some(
|
|
1297
|
+
(line) =>
|
|
1298
|
+
line.scope === 'SELECTED_COMPONENTS' && line.selectedComponentIds.length === 0,
|
|
1299
|
+
),
|
|
1120
1300
|
attributionSummary: flowUi?.partnerAttributionSummary, attributionConfirmLabel: flowUi?.partnerAttributionConfirmLabel,
|
|
1121
1301
|
attributionConfirmed: partnerAttributionConfirmed,
|
|
1122
1302
|
onAttributionConfirmedChange: setPartnerAttributionConfirmed,
|