@ticketboothapp/booking 1.2.123 → 1.2.124
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/CHANGE_BOOKING_BE_HANDOFF.md +15 -4
- package/package.json +1 -1
- package/src/components/booking/AdminChangeBookingContent.tsx +276 -0
- package/src/components/booking/AdminChangeBookingFlow.tsx +651 -6202
- package/src/components/booking/AdminChangeCheckoutDialogs.tsx +105 -0
- package/src/components/booking/AdminChangeCheckoutPanel.tsx +307 -0
- package/src/components/booking/AdminChangePricingPanel.tsx +244 -0
- package/src/components/booking/AdminChangeProductSwitcher.tsx +50 -0
- package/src/components/booking/AdminChangeReceiptComparison.tsx +167 -0
- package/src/components/booking/ChangeBookingCalendarPanel.tsx +126 -0
- package/src/components/booking/ChangeBookingCheckoutPanel.tsx +204 -0
- package/src/components/booking/ChangeBookingFlow.tsx +576 -4648
- package/src/components/booking/ChangeBookingItineraryPanel.tsx +115 -0
- package/src/components/booking/ChangeBookingQuoteStatusPlaceholder.tsx +57 -0
- package/src/components/booking/ChangeBookingSelectionControlsPanel.tsx +157 -0
- package/src/components/booking/ChangeBookingTicketsAndAddOnsPanel.tsx +102 -0
- package/src/components/booking/CheckoutModal.tsx +68 -49
- package/src/components/booking/NewBookingFlow.tsx +485 -3505
- package/src/components/booking/PriceBreakdown.tsx +17 -3
- package/src/components/booking/PriceSummary.tsx +21 -3
- package/src/components/booking/PrivateShuttleAddOnsSection.tsx +102 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +407 -2788
- package/src/components/booking/PrivateShuttleCheckoutDialogs.tsx +106 -0
- package/src/components/booking/PrivateShuttleCheckoutSection.tsx +333 -0
- package/src/components/booking/PrivateShuttleItinerarySection.tsx +95 -0
- package/src/components/booking/PrivateShuttleMediaIntro.tsx +62 -0
- package/src/components/booking/PrivateShuttleOptionSection.tsx +81 -0
- package/src/components/booking/PrivateShuttlePassengerSection.tsx +148 -0
- package/src/components/booking/PrivateShuttlePickupSection.tsx +91 -0
- package/src/components/booking/PrivateShuttlePreferencesSection.tsx +75 -0
- package/src/components/booking/PrivateShuttleStartTimeSection.tsx +88 -0
- package/src/components/booking/StandardBookingCheckoutDialogs.tsx +106 -0
- package/src/components/booking/StandardBookingCheckoutSection.tsx +185 -0
- package/src/components/booking/StandardBookingItineraryPanel.tsx +125 -0
- package/src/components/booking/StandardBookingMediaIntro.tsx +69 -0
- package/src/components/booking/StandardBookingSelectionControlsPanel.tsx +236 -0
- package/src/components/booking/admin-change-customer-quote-runner.ts +119 -0
- package/src/components/booking/admin-change-flow-state-helpers.ts +124 -0
- package/src/components/booking/admin-change-payment-choice-runner.ts +172 -0
- package/src/components/booking/admin-change-provider-payload.ts +101 -0
- package/src/components/booking/change-booking-checkout-builders.ts +185 -0
- package/src/components/booking/change-booking-checkout-quote-runner.ts +207 -0
- package/src/components/booking/change-booking-flow-helpers.ts +706 -0
- package/src/components/booking/change-booking-payment-modal-builders.ts +151 -0
- package/src/components/booking/change-booking-quote-guards.ts +223 -0
- package/src/components/booking/change-booking-quote-state.ts +64 -0
- package/src/components/booking/pricing-v2-checkout-summary.ts +144 -0
- package/src/components/booking/private-shuttle-availability.ts +111 -0
- package/src/components/booking/private-shuttle-checkout-builders.ts +264 -0
- package/src/components/booking/private-shuttle-checkout-controller.ts +744 -0
- package/src/components/booking/private-shuttle-payment-choice-runner.ts +271 -0
- package/src/components/booking/private-shuttle-provider-change-runner.ts +73 -0
- package/src/components/booking/private-shuttle-reservation-runner.ts +260 -0
- package/src/components/booking/standard-booking-availability.ts +305 -0
- package/src/components/booking/standard-booking-checkout-builders.ts +289 -0
- package/src/components/booking/standard-booking-checkout-controller.ts +659 -0
- package/src/components/booking/standard-booking-payment-choice-runner.ts +274 -0
- package/src/components/booking/standard-booking-reservation-runner.ts +229 -0
- package/src/components/booking/use-private-shuttle-availability.ts +602 -0
- package/src/components/booking/use-standard-booking-availability.ts +806 -0
- package/src/components/booking/useActiveProductOptions.ts +26 -0
- package/src/components/booking/useAdminChangeCheckoutController.ts +806 -0
- package/src/components/booking/useAdminChangeOrderSummaryModel.ts +246 -0
- package/src/components/booking/useAdminChangePriceSummary.ts +416 -0
- package/src/components/booking/useAdminChangePricingDebugPanel.tsx +178 -0
- package/src/components/booking/useAdminChangeProductReset.ts +60 -0
- package/src/components/booking/useAdminChangeProducts.ts +102 -0
- package/src/components/booking/useAdminChangePromoDiscount.ts +235 -0
- package/src/components/booking/useAdminChangeProtectedPricing.ts +280 -0
- package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +437 -0
- package/src/components/booking/useAdminChangeQuotePreview.ts +506 -0
- package/src/components/booking/useAdminChangeReceiptDerivation.ts +203 -0
- package/src/components/booking/useAdminCustomReceiptLines.ts +57 -0
- package/src/components/booking/useAdminProviderPricingAdjustments.ts +146 -0
- package/src/components/booking/useBookingAvailabilityAddOns.ts +54 -0
- package/src/components/booking/useBookingPromoAndQuantityController.ts +163 -0
- package/src/components/booking/useBookingQuantityCapTrim.ts +56 -0
- package/src/components/booking/useBookingViewItemAnalytics.ts +30 -0
- package/src/components/booking/useChangeBookingAddOnFloorController.ts +109 -0
- package/src/components/booking/useChangeBookingAutoSelections.ts +148 -0
- package/src/components/booking/useChangeBookingAvailabilities.ts +692 -0
- package/src/components/booking/useChangeBookingCheckoutController.ts +461 -0
- package/src/components/booking/useChangeBookingInitialHydration.ts +204 -0
- package/src/components/booking/useChangeBookingItineraryUiState.ts +133 -0
- package/src/components/booking/useChangeBookingPickupItinerary.ts +472 -0
- package/src/components/booking/useChangeBookingPriceSummary.ts +199 -0
- package/src/components/booking/useChangeBookingPromoDiscount.ts +218 -0
- package/src/components/booking/useChangeBookingProtectedPricing.ts +256 -0
- package/src/components/booking/useChangeBookingQuotePreview.ts +442 -0
- package/src/components/booking/useChangeBookingReceiptDerivation.ts +128 -0
- package/src/components/booking/useChangeBookingReceiptFloorController.ts +449 -0
- package/src/components/booking/useChangeBookingSelection.ts +362 -0
- package/src/components/booking/useChangeBookingSelectionDetails.ts +200 -0
- package/src/components/booking/useChangeBookingSummaryLines.ts +169 -0
- package/src/components/booking/usePrivateShuttlePriceSummary.ts +513 -0
- package/src/components/booking/usePrivateShuttlePromoController.ts +143 -0
- package/src/components/booking/usePrivateShuttlePromoDiscount.ts +124 -0
- package/src/components/booking/useSeedPromoCodeInput.ts +13 -0
- package/src/components/booking/useStandardBookingAutoSelections.ts +224 -0
- package/src/components/booking/useStandardBookingPickupItinerary.ts +473 -0
- package/src/components/booking/useStandardBookingPriceSummary.ts +438 -0
- package/src/components/booking/useStandardBookingPromoController.ts +258 -0
- package/src/components/booking/useStandardBookingPromoDiscount.ts +203 -0
- package/src/data/products-config.json +1 -1
- package/src/lib/booking/change-booking-server-preview.ts +134 -20
- package/src/lib/booking/change-flow-pricing.ts +31 -11
- package/src/lib/booking/checkout-breakdown.ts +66 -1
- package/src/lib/booking/pricing-config.ts +10 -0
- package/src/lib/booking/source-metadata.ts +58 -0
- package/src/lib/booking-api.ts +154 -3
- package/src/lib/pricing-v2-shadow.ts +272 -0
- package/test/change-booking-helpers.test.ts +206 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { Availability, PricingConfig, ReturnOption } from '../../lib/booking-api';
|
|
3
|
+
import {
|
|
4
|
+
resolveChangeFlowDisplayedAmounts,
|
|
5
|
+
roundMoney,
|
|
6
|
+
} from '../../lib/booking/change-flow-pricing';
|
|
7
|
+
import {
|
|
8
|
+
computePricingDriftDelta,
|
|
9
|
+
mergeLineComparisonsWithFullDrift,
|
|
10
|
+
mergePriceSummaryLinesForDrift,
|
|
11
|
+
sumPriceSummaryLinesMajorUnits,
|
|
12
|
+
} from '../../lib/booking/change-booking-pricing-drift';
|
|
13
|
+
import { mapQuoteLineItemsToPriceSummaryLines } from '../../lib/booking/change-booking-server-preview';
|
|
14
|
+
import type { ChangeBookingFlowProps } from './booking-flow-types';
|
|
15
|
+
import type { Currency } from './CurrencySwitcher';
|
|
16
|
+
import type { PriceSummaryLine } from './PriceSummary';
|
|
17
|
+
import type {
|
|
18
|
+
ProviderDashboardChangePricingUi,
|
|
19
|
+
ProviderDashboardPricingLine,
|
|
20
|
+
} from './booking-flow-ui';
|
|
21
|
+
import type { PricingRate } from './change-booking-flow-helpers';
|
|
22
|
+
import { ChangeBookingPricingDriftPanel } from './ChangeBookingPricingDriftPanel';
|
|
23
|
+
import { buildChangeBookingQuoteStatusPlaceholder } from './ChangeBookingQuoteStatusPlaceholder';
|
|
24
|
+
import { useAdminProviderPricingAdjustments } from './useAdminProviderPricingAdjustments';
|
|
25
|
+
import type { AdminCustomReceiptLine } from './useAdminCustomReceiptLines';
|
|
26
|
+
import type { AdminChangeLatestQuote } from './useAdminChangeCheckoutController';
|
|
27
|
+
import { useChangeBookingSelectionDetails } from './useChangeBookingSelectionDetails';
|
|
28
|
+
|
|
29
|
+
type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
|
|
30
|
+
type TicketLineForDisplay = { category?: string | null; qty: number; itemTotal: number };
|
|
31
|
+
|
|
32
|
+
export interface UseAdminChangeQuoteDisplayStateParams {
|
|
33
|
+
suppressSelfServeCurrencyUi: boolean;
|
|
34
|
+
latestChangeQuote: AdminChangeLatestQuote | null;
|
|
35
|
+
selectedAvailability: Availability | null;
|
|
36
|
+
selectedReturnOption: ReturnOption | null;
|
|
37
|
+
totalQuantity: number;
|
|
38
|
+
checkoutPriceSummaryLines: PriceSummaryLine[];
|
|
39
|
+
changeFlowNewBookingTotal: number;
|
|
40
|
+
currency: Currency;
|
|
41
|
+
locale: 'en' | 'fr';
|
|
42
|
+
ticketLineItemsForChangeFlowDisplay: TicketLineForDisplay[];
|
|
43
|
+
pricingForTicketSelector: PricingRate[];
|
|
44
|
+
isTaxIncludedInPrice: boolean;
|
|
45
|
+
effectiveTax: number;
|
|
46
|
+
effectivePromoDiscountAmount: number;
|
|
47
|
+
appliedPromoCode: string | null;
|
|
48
|
+
originalReceipt: ChangeBookingFlowProps['originalReceipt'];
|
|
49
|
+
t: (key: string) => string;
|
|
50
|
+
selfServePricingConfirmed: boolean;
|
|
51
|
+
changeQuoteLoading: boolean;
|
|
52
|
+
changeQuoteFetchError: string | null;
|
|
53
|
+
initialValues: ChangeBookingFlowProps['initialValues'];
|
|
54
|
+
implicitReturnBaselineId: string | null;
|
|
55
|
+
pickupLocationId: string | null;
|
|
56
|
+
quantities: Record<string, number>;
|
|
57
|
+
addOnSelections: AddOnSelection[];
|
|
58
|
+
initialAddOnMinQtyByKey: Map<string, number>;
|
|
59
|
+
changeFlowResolvedInitialProductOptionId: string | null;
|
|
60
|
+
selectedParentProductChanged: boolean;
|
|
61
|
+
isProviderDashboardChange: boolean;
|
|
62
|
+
showProviderPricingInlineEditor: boolean;
|
|
63
|
+
providerPricingUi?: ProviderDashboardChangePricingUi;
|
|
64
|
+
providerQuotedLines: ProviderDashboardPricingLine[];
|
|
65
|
+
editableSummaryPreSubtotalDelta: number;
|
|
66
|
+
adminCustomAdjustmentTotal: number;
|
|
67
|
+
adminCustomReceiptLines: AdminCustomReceiptLine[];
|
|
68
|
+
isManualOverrideEligibleLine: (line: ProviderDashboardPricingLine) => boolean;
|
|
69
|
+
isAdmin: boolean;
|
|
70
|
+
useAdminFeAuthoritativeQuote: boolean;
|
|
71
|
+
isCustomerSelfServeChange: boolean;
|
|
72
|
+
effectiveSubtotalForCheckout: number;
|
|
73
|
+
pricingConfig: PricingConfig | null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function useAdminChangeQuoteDisplayState({
|
|
77
|
+
suppressSelfServeCurrencyUi,
|
|
78
|
+
latestChangeQuote,
|
|
79
|
+
selectedAvailability,
|
|
80
|
+
selectedReturnOption,
|
|
81
|
+
totalQuantity,
|
|
82
|
+
checkoutPriceSummaryLines,
|
|
83
|
+
changeFlowNewBookingTotal,
|
|
84
|
+
currency,
|
|
85
|
+
locale,
|
|
86
|
+
ticketLineItemsForChangeFlowDisplay,
|
|
87
|
+
pricingForTicketSelector,
|
|
88
|
+
isTaxIncludedInPrice,
|
|
89
|
+
effectiveTax,
|
|
90
|
+
effectivePromoDiscountAmount,
|
|
91
|
+
appliedPromoCode,
|
|
92
|
+
originalReceipt,
|
|
93
|
+
t,
|
|
94
|
+
selfServePricingConfirmed,
|
|
95
|
+
changeQuoteLoading,
|
|
96
|
+
changeQuoteFetchError,
|
|
97
|
+
initialValues,
|
|
98
|
+
implicitReturnBaselineId,
|
|
99
|
+
pickupLocationId,
|
|
100
|
+
quantities,
|
|
101
|
+
addOnSelections,
|
|
102
|
+
initialAddOnMinQtyByKey,
|
|
103
|
+
changeFlowResolvedInitialProductOptionId,
|
|
104
|
+
selectedParentProductChanged,
|
|
105
|
+
isProviderDashboardChange,
|
|
106
|
+
showProviderPricingInlineEditor,
|
|
107
|
+
providerPricingUi,
|
|
108
|
+
providerQuotedLines,
|
|
109
|
+
editableSummaryPreSubtotalDelta,
|
|
110
|
+
adminCustomAdjustmentTotal,
|
|
111
|
+
adminCustomReceiptLines,
|
|
112
|
+
isManualOverrideEligibleLine,
|
|
113
|
+
isAdmin,
|
|
114
|
+
useAdminFeAuthoritativeQuote,
|
|
115
|
+
isCustomerSelfServeChange,
|
|
116
|
+
effectiveSubtotalForCheckout,
|
|
117
|
+
pricingConfig,
|
|
118
|
+
}: UseAdminChangeQuoteDisplayStateParams) {
|
|
119
|
+
/** When quote blocks checkout, compare FE vs server lines for debugging (prefers `pricingDriftDetail` from API when sent). */
|
|
120
|
+
const quoteBlockedPricingDrift = useMemo(() => {
|
|
121
|
+
if (!suppressSelfServeCurrencyUi || latestChangeQuote?.canProceed !== false) return null;
|
|
122
|
+
if (!selectedAvailability || totalQuantity <= 0) return null;
|
|
123
|
+
|
|
124
|
+
const api = latestChangeQuote.pricingDriftDetail;
|
|
125
|
+
const currencyForFmt = (latestChangeQuote.currency ?? currency) as Currency;
|
|
126
|
+
|
|
127
|
+
const clientMappedFromApi = mapQuoteLineItemsToPriceSummaryLines(api?.clientLineItems);
|
|
128
|
+
const useBeClientLines = clientMappedFromApi.length > 0;
|
|
129
|
+
/**
|
|
130
|
+
* Checkout passes tax/discount via PriceSummary props, not always as `priceSummaryLines`. Include them here so
|
|
131
|
+
* drift rows and their sums align with `changeFlowNewBookingTotal` (subtotal + tax - promo).
|
|
132
|
+
*/
|
|
133
|
+
const clientLinesForMerge: PriceSummaryLine[] = (() => {
|
|
134
|
+
if (useBeClientLines) return clientMappedFromApi;
|
|
135
|
+
const lines: PriceSummaryLine[] = [...checkoutPriceSummaryLines];
|
|
136
|
+
const hasTaxLine = lines.some(
|
|
137
|
+
(l) => l.kind === 'line' && String(l.type ?? '').toUpperCase() === 'TAX',
|
|
138
|
+
);
|
|
139
|
+
if (!hasTaxLine && !isTaxIncludedInPrice && Math.abs(effectiveTax) >= 0.005) {
|
|
140
|
+
lines.push({
|
|
141
|
+
kind: 'line',
|
|
142
|
+
label: t('booking.tax') !== 'booking.tax' ? t('booking.tax') : 'Taxes and fees',
|
|
143
|
+
amount: effectiveTax,
|
|
144
|
+
type: 'TAX',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const hasPromoSummaryLine = lines.some(
|
|
148
|
+
(l) =>
|
|
149
|
+
l.kind === 'line' &&
|
|
150
|
+
(/PROMO|DISCOUNT|VOUCHER|GIFT/i.test(String(l.type ?? '')) ||
|
|
151
|
+
(l.amount < -0.005 && /promo|discount/i.test(l.label))),
|
|
152
|
+
);
|
|
153
|
+
if (!hasPromoSummaryLine && Math.abs(effectivePromoDiscountAmount) >= 0.005) {
|
|
154
|
+
const trimmedPromoCode = appliedPromoCode?.trim() ?? '';
|
|
155
|
+
const promoLabel =
|
|
156
|
+
trimmedPromoCode.length > 0
|
|
157
|
+
? `Promo: ${trimmedPromoCode}`
|
|
158
|
+
: originalReceipt?.promoLabel?.trim() || 'Discount';
|
|
159
|
+
lines.push({
|
|
160
|
+
kind: 'line',
|
|
161
|
+
label: promoLabel,
|
|
162
|
+
amount: -effectivePromoDiscountAmount,
|
|
163
|
+
type: 'PROMO_CODE',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return lines;
|
|
167
|
+
})();
|
|
168
|
+
|
|
169
|
+
const serverMappedFromApi = mapQuoteLineItemsToPriceSummaryLines(api?.serverLineItems);
|
|
170
|
+
const useBeServerLines = serverMappedFromApi.length > 0;
|
|
171
|
+
const serverLinesForMerge = useBeServerLines
|
|
172
|
+
? serverMappedFromApi
|
|
173
|
+
: (latestChangeQuote.serverPreview?.priceSummaryLines ?? []);
|
|
174
|
+
|
|
175
|
+
const clientTotalForDrift =
|
|
176
|
+
api?.clientTotalMajorUnits != null && Number.isFinite(api.clientTotalMajorUnits)
|
|
177
|
+
? api.clientTotalMajorUnits
|
|
178
|
+
: useBeClientLines
|
|
179
|
+
? sumPriceSummaryLinesMajorUnits(clientMappedFromApi)
|
|
180
|
+
: changeFlowNewBookingTotal;
|
|
181
|
+
|
|
182
|
+
let totalDelta: number | null = null;
|
|
183
|
+
if (api?.deltaMajorUnits != null && Number.isFinite(api.deltaMajorUnits)) {
|
|
184
|
+
totalDelta = roundMoney(api.deltaMajorUnits);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Server "price check" total: explicit API fields -> sum of BE server lines -> derive from delta -> receipt preview totals.
|
|
189
|
+
*/
|
|
190
|
+
let serverTotalFromQuote: number | undefined =
|
|
191
|
+
api?.serverTotalMajorUnits != null && Number.isFinite(api.serverTotalMajorUnits)
|
|
192
|
+
? api.serverTotalMajorUnits
|
|
193
|
+
: undefined;
|
|
194
|
+
if (serverTotalFromQuote == null && useBeServerLines) {
|
|
195
|
+
serverTotalFromQuote = sumPriceSummaryLinesMajorUnits(serverMappedFromApi);
|
|
196
|
+
}
|
|
197
|
+
if (serverTotalFromQuote == null && totalDelta != null) {
|
|
198
|
+
serverTotalFromQuote = roundMoney(clientTotalForDrift - totalDelta);
|
|
199
|
+
}
|
|
200
|
+
if (serverTotalFromQuote == null) {
|
|
201
|
+
serverTotalFromQuote =
|
|
202
|
+
latestChangeQuote.serverDisplay?.total ??
|
|
203
|
+
latestChangeQuote.serverPreview?.totalNewBooking ??
|
|
204
|
+
undefined;
|
|
205
|
+
}
|
|
206
|
+
if (totalDelta == null && serverTotalFromQuote != null && Number.isFinite(serverTotalFromQuote)) {
|
|
207
|
+
totalDelta = roundMoney(clientTotalForDrift - serverTotalFromQuote);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const mergedForDrift = mergePriceSummaryLinesForDrift(clientLinesForMerge, serverLinesForMerge);
|
|
211
|
+
const rows =
|
|
212
|
+
api?.lineComparisons && api.lineComparisons.length > 0
|
|
213
|
+
? mergeLineComparisonsWithFullDrift(api.lineComparisons, mergedForDrift)
|
|
214
|
+
: mergedForDrift;
|
|
215
|
+
|
|
216
|
+
const receiptAnchoring = api?.receiptAnchoring;
|
|
217
|
+
let driftRows = rows;
|
|
218
|
+
if (receiptAnchoring) {
|
|
219
|
+
const adj = roundMoney(
|
|
220
|
+
receiptAnchoring.reconciledTotalMajorUnits -
|
|
221
|
+
receiptAnchoring.requestedCatalogLineSumMajorUnits,
|
|
222
|
+
);
|
|
223
|
+
if (Math.abs(adj) >= 0.005) {
|
|
224
|
+
driftRows = [
|
|
225
|
+
...rows,
|
|
226
|
+
{
|
|
227
|
+
key: 'meta:receipt-anchoring',
|
|
228
|
+
label: 'Receipt anchoring adjustment (BE only - not included in app total)',
|
|
229
|
+
clientAmount: null,
|
|
230
|
+
serverAmount: adj,
|
|
231
|
+
delta: computePricingDriftDelta(null, adj),
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const hasTotalDelta = totalDelta != null && Math.abs(totalDelta) >= 0.005;
|
|
238
|
+
|
|
239
|
+
if (driftRows.length === 0 && !hasTotalDelta) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const usesBeLinePayload = useBeClientLines || useBeServerLines;
|
|
244
|
+
|
|
245
|
+
const ticketCartDetail = ticketLineItemsForChangeFlowDisplay
|
|
246
|
+
.filter((l) => (l.qty ?? 0) > 0)
|
|
247
|
+
.map((line) => {
|
|
248
|
+
const cat = line.category?.trim().toUpperCase() ?? '';
|
|
249
|
+
const rate = pricingForTicketSelector.find((r) => r.category.toUpperCase() === cat);
|
|
250
|
+
const rawList = rate?.baseInDisplayCurrency ?? rate?.priceCAD;
|
|
251
|
+
const listUnit =
|
|
252
|
+
rawList != null && Number.isFinite(Number(rawList))
|
|
253
|
+
? Number(rawList)
|
|
254
|
+
: line.qty > 0
|
|
255
|
+
? line.itemTotal / line.qty
|
|
256
|
+
: 0;
|
|
257
|
+
return {
|
|
258
|
+
category: cat,
|
|
259
|
+
qty: line.qty,
|
|
260
|
+
listUnitMajor: roundMoney(listUnit),
|
|
261
|
+
effectiveUnitMajor: roundMoney(line.qty > 0 ? line.itemTotal / line.qty : 0),
|
|
262
|
+
lineTotalMajor: line.itemTotal,
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<ChangeBookingPricingDriftPanel
|
|
268
|
+
rows={driftRows}
|
|
269
|
+
clientTotal={clientTotalForDrift}
|
|
270
|
+
serverTotal={serverTotalFromQuote}
|
|
271
|
+
totalDelta={totalDelta}
|
|
272
|
+
currency={currencyForFmt}
|
|
273
|
+
locale={locale}
|
|
274
|
+
ticketCartDetail={ticketCartDetail.length > 0 ? ticketCartDetail : undefined}
|
|
275
|
+
serverTicketPricingTrace={latestChangeQuote.ticketPricingTrace ?? undefined}
|
|
276
|
+
receiptAnchoring={receiptAnchoring}
|
|
277
|
+
footnote={
|
|
278
|
+
usesBeLinePayload
|
|
279
|
+
? 'Lines use pricingDriftDetail.clientLineItems / serverLineItems when the quote includes them; totals prefer explicit major-unit fields, then sums of those lines, then the live cart / receipt preview.'
|
|
280
|
+
: undefined
|
|
281
|
+
}
|
|
282
|
+
/>
|
|
283
|
+
);
|
|
284
|
+
}, [
|
|
285
|
+
suppressSelfServeCurrencyUi,
|
|
286
|
+
latestChangeQuote,
|
|
287
|
+
selectedAvailability,
|
|
288
|
+
totalQuantity,
|
|
289
|
+
checkoutPriceSummaryLines,
|
|
290
|
+
changeFlowNewBookingTotal,
|
|
291
|
+
currency,
|
|
292
|
+
locale,
|
|
293
|
+
ticketLineItemsForChangeFlowDisplay,
|
|
294
|
+
pricingForTicketSelector,
|
|
295
|
+
isTaxIncludedInPrice,
|
|
296
|
+
effectiveTax,
|
|
297
|
+
effectivePromoDiscountAmount,
|
|
298
|
+
appliedPromoCode,
|
|
299
|
+
originalReceipt?.promoLabel,
|
|
300
|
+
t,
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const selfServeCheckoutPlaceholder = buildChangeBookingQuoteStatusPlaceholder({
|
|
304
|
+
suppressSelfServeCurrencyUi,
|
|
305
|
+
hasSelectedAvailability: selectedAvailability != null,
|
|
306
|
+
totalQuantity,
|
|
307
|
+
selfServePricingConfirmed,
|
|
308
|
+
changeQuoteLoading,
|
|
309
|
+
changeQuoteFetchError,
|
|
310
|
+
quoteCanProceed: latestChangeQuote?.canProceed,
|
|
311
|
+
quoteBlockedReason: latestChangeQuote?.reasonIfBlocked,
|
|
312
|
+
blockedDetails: quoteBlockedPricingDrift,
|
|
313
|
+
t,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const changeSelectionDetails = useChangeBookingSelectionDetails({
|
|
317
|
+
initialValues,
|
|
318
|
+
selectedAvailability,
|
|
319
|
+
selectedReturnOption,
|
|
320
|
+
implicitReturnBaselineId,
|
|
321
|
+
pickupLocationId,
|
|
322
|
+
quantities,
|
|
323
|
+
addOnSelections,
|
|
324
|
+
initialAddOnMinQtyByKey,
|
|
325
|
+
changeFlowResolvedInitialProductOptionId,
|
|
326
|
+
productChanged: selectedParentProductChanged,
|
|
327
|
+
});
|
|
328
|
+
const hasChangeSelection =
|
|
329
|
+
isProviderDashboardChange
|
|
330
|
+
? changeSelectionDetails.hasOperationalChangesFromInitial
|
|
331
|
+
: changeSelectionDetails.hasChangesFromInitial;
|
|
332
|
+
|
|
333
|
+
const {
|
|
334
|
+
providerTotalsPreview,
|
|
335
|
+
hasEffectiveChangeSelection,
|
|
336
|
+
providerPricingOverrides,
|
|
337
|
+
adminCustomLinesAsAdditionalAdjustments,
|
|
338
|
+
mergedProviderAdditionalAdjustments,
|
|
339
|
+
} = useAdminProviderPricingAdjustments({
|
|
340
|
+
isProviderDashboardChange,
|
|
341
|
+
showProviderPricingInlineEditor,
|
|
342
|
+
providerPricingUi,
|
|
343
|
+
providerQuotedLines,
|
|
344
|
+
hasChangeSelection,
|
|
345
|
+
editableSummaryPreSubtotalDelta,
|
|
346
|
+
adminCustomAdjustmentTotal,
|
|
347
|
+
adminCustomReceiptLines,
|
|
348
|
+
isManualOverrideEligibleLine,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const unchangedReceiptAnchor = useMemo(
|
|
352
|
+
() =>
|
|
353
|
+
originalReceipt && !hasEffectiveChangeSelection
|
|
354
|
+
? {
|
|
355
|
+
subtotal: roundMoney(originalReceipt.subtotal),
|
|
356
|
+
tax: roundMoney(originalReceipt.tax),
|
|
357
|
+
total: roundMoney(originalReceipt.total),
|
|
358
|
+
}
|
|
359
|
+
: null,
|
|
360
|
+
[hasEffectiveChangeSelection, originalReceipt],
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* True until `quoteChangeBooking` returns a confirmed `serverDisplay` for the current selection.
|
|
365
|
+
* Named so manual price-line UI can align with "still settling final price" without touching ticket/receipt math.
|
|
366
|
+
*/
|
|
367
|
+
const changeFlowFinalPricePending =
|
|
368
|
+
suppressSelfServeCurrencyUi &&
|
|
369
|
+
selectedAvailability != null &&
|
|
370
|
+
totalQuantity > 0 &&
|
|
371
|
+
!selfServePricingConfirmed;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Provider inline edits + admin custom lines: show while waiting on or after the authoritative quote.
|
|
375
|
+
* UI-only gate - does not alter ticket line items or receipt-floor rules.
|
|
376
|
+
*/
|
|
377
|
+
const showChangeFlowManualPriceLines =
|
|
378
|
+
hasEffectiveChangeSelection && (changeFlowFinalPricePending || selfServePricingConfirmed);
|
|
379
|
+
const showAdminCustomLineEditor =
|
|
380
|
+
isAdmin &&
|
|
381
|
+
selectedAvailability != null &&
|
|
382
|
+
totalQuantity > 0;
|
|
383
|
+
|
|
384
|
+
const displayedChangeAmountsRaw = resolveChangeFlowDisplayedAmounts({
|
|
385
|
+
providerPreview: providerTotalsPreview,
|
|
386
|
+
serverQuotePreview:
|
|
387
|
+
!useAdminFeAuthoritativeQuote && isCustomerSelfServeChange && latestChangeQuote?.serverDisplay
|
|
388
|
+
? latestChangeQuote.serverDisplay
|
|
389
|
+
: null,
|
|
390
|
+
fromCart: {
|
|
391
|
+
total: changeFlowNewBookingTotal,
|
|
392
|
+
subtotal: effectiveSubtotalForCheckout,
|
|
393
|
+
tax: effectiveTax,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
/** When cart drives display, `effectiveSubtotalForCheckout` already includes admin adjustments - do not add twice. */
|
|
397
|
+
const displayLayerUsesExternalPricing =
|
|
398
|
+
Boolean(providerTotalsPreview) ||
|
|
399
|
+
(!useAdminFeAuthoritativeQuote && isCustomerSelfServeChange && Boolean(latestChangeQuote?.serverDisplay));
|
|
400
|
+
const adminTaxDeltaForExternalDisplay =
|
|
401
|
+
displayLayerUsesExternalPricing && !isTaxIncludedInPrice && Math.abs(adminCustomAdjustmentTotal) >= 0.0005
|
|
402
|
+
? roundMoney(adminCustomAdjustmentTotal * (pricingConfig?.taxRate ?? 0))
|
|
403
|
+
: 0;
|
|
404
|
+
const displayChangeFlowTax = roundMoney(
|
|
405
|
+
unchangedReceiptAnchor
|
|
406
|
+
? unchangedReceiptAnchor.tax
|
|
407
|
+
: displayedChangeAmountsRaw.tax + adminTaxDeltaForExternalDisplay
|
|
408
|
+
);
|
|
409
|
+
const displayChangeFlowProposedTotal = roundMoney(
|
|
410
|
+
unchangedReceiptAnchor
|
|
411
|
+
? unchangedReceiptAnchor.total
|
|
412
|
+
: displayLayerUsesExternalPricing
|
|
413
|
+
? displayedChangeAmountsRaw.total + adminCustomAdjustmentTotal + adminTaxDeltaForExternalDisplay
|
|
414
|
+
: displayedChangeAmountsRaw.total
|
|
415
|
+
);
|
|
416
|
+
const displayChangeFlowProposedTotalWithEditableLines = roundMoney(
|
|
417
|
+
displayChangeFlowProposedTotal + editableSummaryPreSubtotalDelta
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
selfServeCheckoutPlaceholder,
|
|
422
|
+
changeSelectionDetails,
|
|
423
|
+
hasChangeSelection,
|
|
424
|
+
hasEffectiveChangeSelection,
|
|
425
|
+
providerPricingOverrides,
|
|
426
|
+
adminCustomLinesAsAdditionalAdjustments,
|
|
427
|
+
mergedProviderAdditionalAdjustments,
|
|
428
|
+
unchangedReceiptAnchor,
|
|
429
|
+
showChangeFlowManualPriceLines,
|
|
430
|
+
showAdminCustomLineEditor,
|
|
431
|
+
displayedChangeAmountsRaw,
|
|
432
|
+
displayLayerUsesExternalPricing,
|
|
433
|
+
displayChangeFlowTax,
|
|
434
|
+
displayChangeFlowProposedTotal,
|
|
435
|
+
displayChangeFlowProposedTotalWithEditableLines,
|
|
436
|
+
};
|
|
437
|
+
}
|