@ticketboothapp/booking 1.2.164 → 1.2.165
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
CHANGED
|
@@ -43,3 +43,16 @@ export function buildAdminChangeQuoteRequestKey(input: AdminChangeQuoteRequestKe
|
|
|
43
43
|
useAdminFeAuthoritativeQuote: input.useAdminFeAuthoritativeQuote,
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A completed request is reusable only while its authoritative quote is still published.
|
|
49
|
+
* Closing a nested payment dialog can clear transient quote state without changing the
|
|
50
|
+
* selection inputs; in that case the same input key must be eligible for a refetch.
|
|
51
|
+
*/
|
|
52
|
+
export function canReuseCompletedAdminChangeQuote(
|
|
53
|
+
inputKey: string,
|
|
54
|
+
lastCompletedInputKey: string | null,
|
|
55
|
+
hasPublishedQuote: boolean,
|
|
56
|
+
): boolean {
|
|
57
|
+
return Boolean(inputKey) && lastCompletedInputKey === inputKey && hasPublishedQuote;
|
|
58
|
+
}
|
|
@@ -290,7 +290,8 @@ export function useAdminChangeCheckoutController({
|
|
|
290
290
|
setShowCheckoutModal(false);
|
|
291
291
|
setCheckoutModalData(null);
|
|
292
292
|
setCheckoutClientSecret('');
|
|
293
|
-
|
|
293
|
+
setError('');
|
|
294
|
+
}, [setError]);
|
|
294
295
|
|
|
295
296
|
const cancelPendingReservationBestEffort = useCallback(() => {
|
|
296
297
|
if (paymentSubmitInFlightRef.current) return;
|
|
@@ -27,7 +27,10 @@ import {
|
|
|
27
27
|
} from './change-booking-flow-helpers';
|
|
28
28
|
import type { Currency } from './CurrencySwitcher';
|
|
29
29
|
import type { AdminChangeLatestQuote } from './useAdminChangeCheckoutController';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
buildAdminChangeQuoteRequestKey,
|
|
32
|
+
canReuseCompletedAdminChangeQuote,
|
|
33
|
+
} from './admin-change-quote-request-key';
|
|
31
34
|
import type { ProviderDashboardChangeBookingPayload } from './provider-dashboard-change-booking';
|
|
32
35
|
|
|
33
36
|
type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
|
|
@@ -167,6 +170,7 @@ export function useAdminChangeQuotePreview({
|
|
|
167
170
|
latestChangeQuote?.canProceed === false &&
|
|
168
171
|
(latestChangeQuote.refundDecisionOperations?.length ?? 0) === 0;
|
|
169
172
|
const refundDecisionRequired = (latestChangeQuote?.refundDecisionOperations?.length ?? 0) > 0;
|
|
173
|
+
const hasPublishedChangeQuote = latestChangeQuote !== null;
|
|
170
174
|
const changeQuoteInputsKey = useMemo(() => buildAdminChangeQuoteRequestKey({
|
|
171
175
|
bookingReference: initialValues?.bookingReference,
|
|
172
176
|
lastName,
|
|
@@ -208,6 +212,14 @@ export function useAdminChangeQuotePreview({
|
|
|
208
212
|
const changeCheckoutButtonLabel = (() => {
|
|
209
213
|
if (!hasEffectiveChangeSelection) return undefined;
|
|
210
214
|
if (isProviderDashboardChange) {
|
|
215
|
+
if (changeQuoteLoading || (!latestChangeQuote && !changeQuoteFetchError)) {
|
|
216
|
+
const tr = t('booking.updatingPrice');
|
|
217
|
+
return tr !== 'booking.updatingPrice' ? tr : 'Updating price\u2026';
|
|
218
|
+
}
|
|
219
|
+
if (changeQuoteFetchError) {
|
|
220
|
+
const tr = t('booking.changeBookingRetry');
|
|
221
|
+
return tr !== 'booking.changeBookingRetry' ? tr : 'Change booking (retry)';
|
|
222
|
+
}
|
|
211
223
|
if (isChangeQuoteBlocked) {
|
|
212
224
|
const tr = t('booking.changeBookingNotAvailable');
|
|
213
225
|
return tr !== 'booking.changeBookingNotAvailable' ? tr : 'Change not available';
|
|
@@ -215,8 +227,8 @@ export function useAdminChangeQuotePreview({
|
|
|
215
227
|
// Prefer authoritative V2 signed due (charge positive / refund negative). Do not use the
|
|
216
228
|
// FE cart estimate once a server quote exists — receipt total deltas can disagree with
|
|
217
229
|
// floored/settlement amounts (DEFECT-PV2-018 follow-up).
|
|
218
|
-
const serverDue = latestChangeQuote?.serverPreview?.amountDue;
|
|
219
|
-
const due = Math.round(
|
|
230
|
+
const serverDue = latestChangeQuote?.serverPreview?.amountDue ?? latestChangeQuote?.priceDiff ?? 0;
|
|
231
|
+
const due = Math.round(serverDue * 100) / 100;
|
|
220
232
|
return due > 0
|
|
221
233
|
? `Change booking (${formatCurrencyAmount(due, currency, locale as 'en' | 'fr')})`
|
|
222
234
|
: due < 0
|
|
@@ -398,11 +410,15 @@ export function useAdminChangeQuotePreview({
|
|
|
398
410
|
return;
|
|
399
411
|
}
|
|
400
412
|
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
413
|
+
if (changeQuoteInputsKey && inFlightQuoteInputsKeyRef.current === changeQuoteInputsKey) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (canReuseCompletedAdminChangeQuote(
|
|
418
|
+
changeQuoteInputsKey,
|
|
419
|
+
lastCompletedQuoteInputsKeyRef.current,
|
|
420
|
+
hasPublishedChangeQuote,
|
|
421
|
+
)) {
|
|
406
422
|
setChangeQuoteLoading(false);
|
|
407
423
|
return;
|
|
408
424
|
}
|
|
@@ -493,10 +509,13 @@ export function useAdminChangeQuotePreview({
|
|
|
493
509
|
}, [
|
|
494
510
|
// Fetching is controlled by the canonical user-input identity. Do not add quote-derived
|
|
495
511
|
// totals, receipt objects, or display lines here: a response must not trigger another quote.
|
|
512
|
+
// Quote presence is intentionally included so a cleared transient quote can be restored by
|
|
513
|
+
// refetching the same canonical input identity after a nested checkout is closed.
|
|
496
514
|
isCustomerSelfServeChange,
|
|
497
515
|
hasChangeSelection,
|
|
498
516
|
missingRequiredReturnSelection,
|
|
499
517
|
changeQuoteInputsKey,
|
|
518
|
+
hasPublishedChangeQuote,
|
|
500
519
|
]);
|
|
501
520
|
|
|
502
521
|
return {
|
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
mapAdminChangeBookingQuoteV2Data,
|
|
14
14
|
type ChangeBookingQuoteResponse,
|
|
15
15
|
} from '../src/lib/booking-api';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
buildAdminChangeQuoteRequestKey,
|
|
18
|
+
canReuseCompletedAdminChangeQuote,
|
|
19
|
+
} from '../src/components/booking/admin-change-quote-request-key';
|
|
17
20
|
import {
|
|
18
21
|
noRefundRemovedValue,
|
|
19
22
|
normalizeReturnPriceTreatment,
|
|
@@ -195,6 +198,13 @@ test('admin quote response display state cannot invalidate its request identity'
|
|
|
195
198
|
assert.equal(keyAfterResponse, keyBeforeResponse);
|
|
196
199
|
});
|
|
197
200
|
|
|
201
|
+
test('admin quote dedupe refetches unchanged inputs after the published quote is cleared', () => {
|
|
202
|
+
const inputKey = 'same-admin-change-inputs';
|
|
203
|
+
assert.equal(canReuseCompletedAdminChangeQuote(inputKey, inputKey, true), true);
|
|
204
|
+
assert.equal(canReuseCompletedAdminChangeQuote(inputKey, inputKey, false), false);
|
|
205
|
+
assert.equal(canReuseCompletedAdminChangeQuote(inputKey, 'different-inputs', true), false);
|
|
206
|
+
});
|
|
207
|
+
|
|
198
208
|
test('admin no-charge provider payload carries the selected pickup itinerary', () => {
|
|
199
209
|
const itineraryDisplay: NonNullable<
|
|
200
210
|
Parameters<typeof buildAdminChangeProviderPayload>[0]['itineraryDisplay']
|