@ticketboothapp/booking 1.2.186 → 1.2.187
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 +2 -0
- package/src/components/booking/AdminChangeBookingFlow.tsx +7 -0
- package/src/components/booking/AdminChangeCheckoutPanel.tsx +6 -0
- package/src/components/booking/AdminChangeReceiptComparison.tsx +39 -9
- package/src/components/booking/admin-change-provider-payload.ts +9 -5
- package/src/components/booking/admin-change-quote-request-key.ts +2 -0
- package/src/components/booking/admin-change-v2-quote-request.ts +6 -3
- package/src/components/booking/provider-dashboard-change-booking.ts +2 -0
- package/src/components/booking/useAdminChangeCheckoutController.ts +3 -0
- package/src/components/booking/useAdminChangeQuotePreview.ts +5 -0
- package/src/lib/booking-api.ts +2 -0
- package/test/change-booking-helpers.test.ts +75 -0
package/package.json
CHANGED
|
@@ -197,6 +197,8 @@ const checkoutPanelPropKeys = [
|
|
|
197
197
|
'onRemoveAdminCustomReceiptLine',
|
|
198
198
|
'onApplyAdminCustomReceiptLines',
|
|
199
199
|
'onSetAdminReceiptLineOverride',
|
|
200
|
+
'preserveExistingReceipt',
|
|
201
|
+
'onPreserveExistingReceiptChange',
|
|
200
202
|
'firstName',
|
|
201
203
|
'lastName',
|
|
202
204
|
'email',
|
|
@@ -348,6 +348,7 @@ export function AdminChangeBookingFlow({
|
|
|
348
348
|
>({});
|
|
349
349
|
const [changeQuoteLoading, setChangeQuoteLoading] = useState(false);
|
|
350
350
|
const [changeQuoteFetchError, setChangeQuoteFetchError] = useState<string | null>(null);
|
|
351
|
+
const [preserveExistingReceipt, setPreserveExistingReceipt] = useState(false);
|
|
351
352
|
/** Dedupe quote calls while user input payload is unchanged. */
|
|
352
353
|
const lastCompletedQuoteInputsKeyRef = useRef<string | null>(null);
|
|
353
354
|
const inFlightQuoteInputsKeyRef = useRef<string | null>(null);
|
|
@@ -804,6 +805,7 @@ export function AdminChangeBookingFlow({
|
|
|
804
805
|
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
805
806
|
structuredAdjustments: adminStructuredAdjustments,
|
|
806
807
|
reverseAdjustmentIds: adminReverseAdjustmentIds,
|
|
808
|
+
preserveExistingReceipt,
|
|
807
809
|
useAdminFeAuthoritativeQuote,
|
|
808
810
|
}), [
|
|
809
811
|
initialValues?.bookingReference,
|
|
@@ -820,6 +822,7 @@ export function AdminChangeBookingFlow({
|
|
|
820
822
|
appliedAdminCustomReceiptLines,
|
|
821
823
|
adminStructuredAdjustments,
|
|
822
824
|
adminReverseAdjustmentIds,
|
|
825
|
+
preserveExistingReceipt,
|
|
823
826
|
useAdminFeAuthoritativeQuote,
|
|
824
827
|
]);
|
|
825
828
|
const responseRefundDecisionOperations = latestChangeQuote?.refundDecisionOperations ?? [];
|
|
@@ -931,6 +934,7 @@ export function AdminChangeBookingFlow({
|
|
|
931
934
|
adminCustomReceiptLines: appliedAdminCustomReceiptLines,
|
|
932
935
|
adminStructuredAdjustments,
|
|
933
936
|
adminReverseAdjustmentIds,
|
|
937
|
+
preserveExistingReceipt,
|
|
934
938
|
needsAdjustmentDiscovery:
|
|
935
939
|
isProviderDashboardChange && !hasHydratedExistingAdjustments,
|
|
936
940
|
refundDispositionsByOperationId: activeRefundDispositionsByOperationId,
|
|
@@ -1153,6 +1157,7 @@ export function AdminChangeBookingFlow({
|
|
|
1153
1157
|
mergedProviderAdditionalAdjustments,
|
|
1154
1158
|
adminStructuredAdjustments,
|
|
1155
1159
|
adminReverseAdjustmentIds,
|
|
1160
|
+
preserveExistingReceipt,
|
|
1156
1161
|
refundDispositionsByOperationId: activeRefundDispositionsByOperationId,
|
|
1157
1162
|
providerApplyAuthoritativeReceipt,
|
|
1158
1163
|
onSuccess,
|
|
@@ -1301,6 +1306,8 @@ export function AdminChangeBookingFlow({
|
|
|
1301
1306
|
onRemoveAdminCustomReceiptLine: handleRemoveAdminCustomReceiptLine,
|
|
1302
1307
|
onApplyAdminCustomReceiptLines: handleApplyAdminCustomReceiptLines,
|
|
1303
1308
|
onSetAdminReceiptLineOverride: handleSetAdminReceiptLineOverride,
|
|
1309
|
+
preserveExistingReceipt,
|
|
1310
|
+
onPreserveExistingReceiptChange: setPreserveExistingReceipt,
|
|
1304
1311
|
onFirstNameChange: setFirstName, onLastNameChange: setLastName, onEmailChange: setEmail,
|
|
1305
1312
|
onClearError: () => setError(''),
|
|
1306
1313
|
pickupLocations: product.pickupLocations, destinations: product.destinations, highlightedPickupLocationIds,
|
|
@@ -87,6 +87,8 @@ export interface AdminChangeCheckoutPanelProps {
|
|
|
87
87
|
baseAmount: number;
|
|
88
88
|
targetAmount: number;
|
|
89
89
|
}) => void;
|
|
90
|
+
preserveExistingReceipt: boolean;
|
|
91
|
+
onPreserveExistingReceiptChange: (preserve: boolean) => void;
|
|
90
92
|
firstName: string;
|
|
91
93
|
lastName: string;
|
|
92
94
|
email: string;
|
|
@@ -179,6 +181,8 @@ export function AdminChangeCheckoutPanel({
|
|
|
179
181
|
onRemoveAdminCustomReceiptLine,
|
|
180
182
|
onApplyAdminCustomReceiptLines,
|
|
181
183
|
onSetAdminReceiptLineOverride,
|
|
184
|
+
preserveExistingReceipt,
|
|
185
|
+
onPreserveExistingReceiptChange,
|
|
182
186
|
firstName,
|
|
183
187
|
lastName,
|
|
184
188
|
email,
|
|
@@ -282,6 +286,8 @@ export function AdminChangeCheckoutPanel({
|
|
|
282
286
|
promoEditor={promoEditor}
|
|
283
287
|
promoCode={promoCode}
|
|
284
288
|
adjustments={pricingAdjustments}
|
|
289
|
+
preserveExistingReceipt={preserveExistingReceipt}
|
|
290
|
+
onPreserveExistingReceiptChange={onPreserveExistingReceiptChange}
|
|
285
291
|
adminCustomReceiptLines={adminCustomReceiptLines}
|
|
286
292
|
onSetReceiptLineOverride={onSetAdminReceiptLineOverride}
|
|
287
293
|
/>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, type ReactNode } from 'react';
|
|
1
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
2
2
|
import type {
|
|
3
3
|
AdminAmendmentOperationSnapshot,
|
|
4
4
|
AdminAmendmentRefundDisposition,
|
|
@@ -72,6 +72,8 @@ export interface AdminChangeReceiptComparisonProps {
|
|
|
72
72
|
baseAmount: number;
|
|
73
73
|
targetAmount: number;
|
|
74
74
|
}) => void;
|
|
75
|
+
preserveExistingReceipt?: boolean;
|
|
76
|
+
onPreserveExistingReceiptChange?: (preserve: boolean) => void;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
function normalizeLocale(locale: string): Locale {
|
|
@@ -128,8 +130,13 @@ export function AdminChangeReceiptComparison({
|
|
|
128
130
|
refundQuoteLoading = false,
|
|
129
131
|
adminCustomReceiptLines = [],
|
|
130
132
|
onSetReceiptLineOverride,
|
|
133
|
+
preserveExistingReceipt = false,
|
|
134
|
+
onPreserveExistingReceiptChange,
|
|
131
135
|
}: AdminChangeReceiptComparisonProps) {
|
|
132
136
|
const [overrideDrafts, setOverrideDrafts] = useState<Record<string, string>>({});
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (preserveExistingReceipt) setOverrideDrafts({});
|
|
139
|
+
}, [preserveExistingReceipt]);
|
|
133
140
|
const readableQuoteError = friendlyChangeBookingError(quoteError);
|
|
134
141
|
const displayLocale = normalizeLocale(locale);
|
|
135
142
|
const existingLines = originalLines(originalReceipt);
|
|
@@ -367,15 +374,38 @@ export function AdminChangeReceiptComparison({
|
|
|
367
374
|
</p>
|
|
368
375
|
) : null}
|
|
369
376
|
</div>
|
|
370
|
-
<
|
|
377
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
378
|
+
{onPreserveExistingReceiptChange ? (
|
|
379
|
+
<button
|
|
380
|
+
type="button"
|
|
381
|
+
aria-pressed={preserveExistingReceipt}
|
|
382
|
+
className={`rounded-md border px-2.5 py-1.5 text-xs font-semibold transition ${
|
|
383
|
+
preserveExistingReceipt
|
|
384
|
+
? 'border-sky-700 bg-sky-700 text-white'
|
|
385
|
+
: 'border-stone-300 bg-white text-stone-700 hover:border-sky-500 hover:text-sky-800'
|
|
386
|
+
}`}
|
|
387
|
+
onClick={() => onPreserveExistingReceiptChange(!preserveExistingReceipt)}
|
|
388
|
+
>
|
|
389
|
+
Keep receipt unchanged
|
|
390
|
+
</button>
|
|
391
|
+
) : null}
|
|
392
|
+
<span className="text-xs font-medium text-stone-500">
|
|
371
393
|
{isProductFamilyConversion
|
|
372
394
|
? formatCurrencyAmount(updatedTotal ?? 0, currency, displayLocale)
|
|
373
395
|
: bookingTotalChange == null
|
|
374
396
|
? '—'
|
|
375
397
|
: formatCurrencyAmount(bookingTotalChange, currency, displayLocale)}
|
|
376
|
-
|
|
398
|
+
</span>
|
|
399
|
+
</div>
|
|
377
400
|
</div>
|
|
378
|
-
{
|
|
401
|
+
{preserveExistingReceipt ? (
|
|
402
|
+
<div className="mb-3 rounded-md border border-sky-200 bg-white px-3 py-2">
|
|
403
|
+
<p className="text-sm font-semibold text-stone-800">Receipt and balance will stay unchanged</p>
|
|
404
|
+
<p className="mt-0.5 text-xs text-stone-600">
|
|
405
|
+
Only the booking details will change. No charge, refund, or balance adjustment will be created.
|
|
406
|
+
</p>
|
|
407
|
+
</div>
|
|
408
|
+
) : promoEditor}
|
|
379
409
|
{!selectionChanged ? (
|
|
380
410
|
<div className="rounded-md border border-dashed border-stone-300 bg-white/70 px-3 py-4 text-center">
|
|
381
411
|
<p className="text-sm font-medium text-stone-700">No changes selected</p>
|
|
@@ -416,10 +446,10 @@ export function AdminChangeReceiptComparison({
|
|
|
416
446
|
subtotal={summarySubtotal}
|
|
417
447
|
size="sm"
|
|
418
448
|
t={t}
|
|
419
|
-
lineAmountInputs={{ ...quotedOverrideAmountInputs, ...overrideDrafts }}
|
|
420
|
-
onLineAmountInputChange={handleOverrideChange}
|
|
421
|
-
onLineAmountInputBlur={handleOverrideBlur}
|
|
422
|
-
onLineRemove={handleOverrideRemove}
|
|
449
|
+
lineAmountInputs={preserveExistingReceipt ? undefined : { ...quotedOverrideAmountInputs, ...overrideDrafts }}
|
|
450
|
+
onLineAmountInputChange={preserveExistingReceipt ? undefined : handleOverrideChange}
|
|
451
|
+
onLineAmountInputBlur={preserveExistingReceipt ? undefined : handleOverrideBlur}
|
|
452
|
+
onLineRemove={preserveExistingReceipt ? undefined : handleOverrideRemove}
|
|
423
453
|
/>
|
|
424
454
|
{removedSummaryDisplayLines.length > 0 ? (
|
|
425
455
|
<div className="mt-3 border-t border-stone-200 pt-2 text-xs text-stone-500">
|
|
@@ -446,7 +476,7 @@ export function AdminChangeReceiptComparison({
|
|
|
446
476
|
</section>
|
|
447
477
|
</div>
|
|
448
478
|
|
|
449
|
-
{adjustments ? (
|
|
479
|
+
{!preserveExistingReceipt && adjustments ? (
|
|
450
480
|
<section className="rounded-lg border border-amber-200 bg-amber-50/40 p-3">
|
|
451
481
|
<h3 className="mb-2 text-sm font-semibold text-stone-900">Add custom line items</h3>
|
|
452
482
|
{adjustments}
|
|
@@ -40,6 +40,7 @@ export interface BuildAdminChangeProviderPayloadParams {
|
|
|
40
40
|
mergedProviderAdditionalAdjustments: AdminChangeProviderAdditionalAdjustment[];
|
|
41
41
|
adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
|
|
42
42
|
adminReverseAdjustmentIds?: string[];
|
|
43
|
+
preserveExistingReceipt?: boolean;
|
|
43
44
|
refundDispositionsByOperationId: NonNullable<
|
|
44
45
|
ProviderDashboardChangeBookingPayload['refundDispositionsByOperationId']
|
|
45
46
|
>;
|
|
@@ -69,6 +70,7 @@ export function buildAdminChangeProviderPayload({
|
|
|
69
70
|
mergedProviderAdditionalAdjustments,
|
|
70
71
|
adminStructuredAdjustments,
|
|
71
72
|
adminReverseAdjustmentIds = [],
|
|
73
|
+
preserveExistingReceipt = false,
|
|
72
74
|
refundDispositionsByOperationId,
|
|
73
75
|
pricingV2QuoteId,
|
|
74
76
|
pricingV2QuotedTotal,
|
|
@@ -84,6 +86,7 @@ export function buildAdminChangeProviderPayload({
|
|
|
84
86
|
: null;
|
|
85
87
|
|
|
86
88
|
return {
|
|
89
|
+
preserveExistingReceipt,
|
|
87
90
|
productId: availabilityProductOptionId,
|
|
88
91
|
parentProductId: product.productId,
|
|
89
92
|
availabilityId: selectedAvailability.availabilityId ?? null,
|
|
@@ -102,10 +105,11 @@ export function buildAdminChangeProviderPayload({
|
|
|
102
105
|
cancellationPolicyId: cancellationPolicyId ?? initialCancellationPolicyId ?? null,
|
|
103
106
|
promoCode: appliedPromoCode ?? null,
|
|
104
107
|
promoApplicationScope: appliedPromoCode ? promoApplicationScope : null,
|
|
105
|
-
newTotalAmount,
|
|
108
|
+
newTotalAmount: preserveExistingReceipt ? undefined : newTotalAmount,
|
|
106
109
|
additionalHoursCount: null,
|
|
107
110
|
pricingAdjustment:
|
|
108
|
-
|
|
111
|
+
!preserveExistingReceipt &&
|
|
112
|
+
(providerPricingOverrides.length > 0 || mergedProviderAdditionalAdjustments.length > 0)
|
|
109
113
|
? {
|
|
110
114
|
mode: 'MANUAL_LINES',
|
|
111
115
|
...(providerPricingOverrides.length > 0 ? { lineOverrides: providerPricingOverrides } : {}),
|
|
@@ -115,9 +119,9 @@ export function buildAdminChangeProviderPayload({
|
|
|
115
119
|
}
|
|
116
120
|
: undefined,
|
|
117
121
|
authoritativeReceipt: providerApplyAuthoritativeReceipt,
|
|
118
|
-
structuredAdjustments: adminStructuredAdjustments,
|
|
119
|
-
reverseAdjustmentIds: adminReverseAdjustmentIds,
|
|
120
|
-
refundDispositionsByOperationId,
|
|
122
|
+
structuredAdjustments: preserveExistingReceipt ? [] : adminStructuredAdjustments,
|
|
123
|
+
reverseAdjustmentIds: preserveExistingReceipt ? [] : adminReverseAdjustmentIds,
|
|
124
|
+
refundDispositionsByOperationId: preserveExistingReceipt ? {} : refundDispositionsByOperationId,
|
|
121
125
|
pricingV2QuoteId: pricingV2QuoteId ?? null,
|
|
122
126
|
pricingV2QuotedTotal: pricingV2QuotedTotal ?? null,
|
|
123
127
|
capacitySeatCredit: {
|
|
@@ -16,6 +16,7 @@ interface AdminChangeQuoteRequestKeyInput {
|
|
|
16
16
|
promoApplicationScope?: AdminPromoApplicationScope | null;
|
|
17
17
|
structuredAdjustments?: unknown[];
|
|
18
18
|
reverseAdjustmentIds?: string[];
|
|
19
|
+
preserveExistingReceipt?: boolean;
|
|
19
20
|
refundDispositionsByOperationId?: Record<string, string>;
|
|
20
21
|
useAdminFeAuthoritativeQuote: boolean;
|
|
21
22
|
}
|
|
@@ -74,6 +75,7 @@ export function buildAdminChangeQuoteRequestKey(input: AdminChangeQuoteRequestKe
|
|
|
74
75
|
promoApplicationScope: input.promoCode ? (input.promoApplicationScope ?? 'CHANGE_ONLY') : null,
|
|
75
76
|
structuredAdjustments: input.structuredAdjustments ?? [],
|
|
76
77
|
reverseAdjustmentIds: [...(input.reverseAdjustmentIds ?? [])].sort(),
|
|
78
|
+
preserveExistingReceipt: input.preserveExistingReceipt === true,
|
|
77
79
|
refundDispositionsByOperationId: Object.fromEntries(
|
|
78
80
|
Object.entries(input.refundDispositionsByOperationId ?? {}).sort(([a], [b]) => a.localeCompare(b)),
|
|
79
81
|
),
|
|
@@ -32,6 +32,7 @@ export interface BuildAdminChangeV2QuoteRequestParams {
|
|
|
32
32
|
promoApplicationScope?: AdminPromoApplicationScope;
|
|
33
33
|
structuredAdjustments: NonNullable<ChangeBookingQuoteRequest['structuredAdjustments']>;
|
|
34
34
|
reverseAdjustmentIds?: string[];
|
|
35
|
+
preserveExistingReceipt?: boolean;
|
|
35
36
|
includeSelectableAdjustmentComponents?: boolean;
|
|
36
37
|
refundDispositionsByOperationId: Record<string, AdminAmendmentRefundDisposition>;
|
|
37
38
|
previousPassengerCount: number;
|
|
@@ -59,6 +60,7 @@ export function buildAdminChangeV2QuoteRequest({
|
|
|
59
60
|
promoApplicationScope = 'CHANGE_ONLY',
|
|
60
61
|
structuredAdjustments,
|
|
61
62
|
reverseAdjustmentIds = [],
|
|
63
|
+
preserveExistingReceipt = false,
|
|
62
64
|
includeSelectableAdjustmentComponents = false,
|
|
63
65
|
refundDispositionsByOperationId,
|
|
64
66
|
previousPassengerCount,
|
|
@@ -80,12 +82,13 @@ export function buildAdminChangeV2QuoteRequest({
|
|
|
80
82
|
cancellationPolicyId,
|
|
81
83
|
promoCode: promoCode?.trim() || null,
|
|
82
84
|
promoApplicationScope: promoCode?.trim() ? promoApplicationScope : null,
|
|
83
|
-
structuredAdjustments,
|
|
84
|
-
reverseAdjustmentIds,
|
|
85
|
+
structuredAdjustments: preserveExistingReceipt ? [] : structuredAdjustments,
|
|
86
|
+
reverseAdjustmentIds: preserveExistingReceipt ? [] : reverseAdjustmentIds,
|
|
87
|
+
preserveExistingReceipt,
|
|
85
88
|
...(includeSelectableAdjustmentComponents
|
|
86
89
|
? { includeSelectableAdjustmentComponents: true }
|
|
87
90
|
: {}),
|
|
88
|
-
...(Object.keys(refundDispositionsByOperationId).length > 0
|
|
91
|
+
...(!preserveExistingReceipt && Object.keys(refundDispositionsByOperationId).length > 0
|
|
89
92
|
? { refundDispositionsByOperationId }
|
|
90
93
|
: {}),
|
|
91
94
|
capacitySeatCredit: {
|
|
@@ -33,6 +33,8 @@ export type AdminAmendmentStructuredAdjustment = {
|
|
|
33
33
|
|
|
34
34
|
/** Payload passed to `onChangeBooking` when applying a dashboard-managed booking change. */
|
|
35
35
|
export type ProviderDashboardChangeBookingPayload = {
|
|
36
|
+
/** Apply operational changes while preserving the existing receipt and balance exactly. */
|
|
37
|
+
preserveExistingReceipt?: boolean;
|
|
36
38
|
/** Parent catalog product id. Present when admin changes the booking to a different product. */
|
|
37
39
|
parentProductId?: string;
|
|
38
40
|
/** Product option id selected for the new booking. */
|
|
@@ -155,6 +155,7 @@ export interface UseAdminChangeCheckoutControllerParams {
|
|
|
155
155
|
mergedProviderAdditionalAdjustments: AdminChangeProviderAdditionalAdjustment[];
|
|
156
156
|
adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
|
|
157
157
|
adminReverseAdjustmentIds: string[];
|
|
158
|
+
preserveExistingReceipt: boolean;
|
|
158
159
|
refundDispositionsByOperationId: NonNullable<
|
|
159
160
|
ProviderDashboardChangeBookingPayload['refundDispositionsByOperationId']
|
|
160
161
|
>;
|
|
@@ -258,6 +259,7 @@ export function useAdminChangeCheckoutController({
|
|
|
258
259
|
mergedProviderAdditionalAdjustments,
|
|
259
260
|
adminStructuredAdjustments,
|
|
260
261
|
adminReverseAdjustmentIds,
|
|
262
|
+
preserveExistingReceipt,
|
|
261
263
|
refundDispositionsByOperationId,
|
|
262
264
|
providerApplyAuthoritativeReceipt,
|
|
263
265
|
onSuccess,
|
|
@@ -381,6 +383,7 @@ export function useAdminChangeCheckoutController({
|
|
|
381
383
|
mergedProviderAdditionalAdjustments,
|
|
382
384
|
adminStructuredAdjustments,
|
|
383
385
|
adminReverseAdjustmentIds,
|
|
386
|
+
preserveExistingReceipt,
|
|
384
387
|
refundDispositionsByOperationId,
|
|
385
388
|
pricingV2QuoteId: authoritativeQuote?.quoteId ?? null,
|
|
386
389
|
pricingV2QuotedTotal: authoritativeQuote?.quotedTotal ?? authoritativeQuote?.serverDisplay?.total ?? null,
|
|
@@ -80,6 +80,7 @@ export interface UseAdminChangeQuotePreviewParams {
|
|
|
80
80
|
adminCustomReceiptLines: AdminCustomReceiptLine[];
|
|
81
81
|
adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
|
|
82
82
|
adminReverseAdjustmentIds: string[];
|
|
83
|
+
preserveExistingReceipt: boolean;
|
|
83
84
|
needsAdjustmentDiscovery: boolean;
|
|
84
85
|
refundDispositionsByOperationId: Record<string, AdminAmendmentRefundDisposition>;
|
|
85
86
|
useAdminFeAuthoritativeQuote: boolean;
|
|
@@ -144,6 +145,7 @@ export function useAdminChangeQuotePreview({
|
|
|
144
145
|
adminCustomReceiptLines,
|
|
145
146
|
adminStructuredAdjustments,
|
|
146
147
|
adminReverseAdjustmentIds,
|
|
148
|
+
preserveExistingReceipt,
|
|
147
149
|
needsAdjustmentDiscovery,
|
|
148
150
|
refundDispositionsByOperationId,
|
|
149
151
|
useAdminFeAuthoritativeQuote,
|
|
@@ -238,6 +240,7 @@ export function useAdminChangeQuotePreview({
|
|
|
238
240
|
promoApplicationScope,
|
|
239
241
|
structuredAdjustments: adminStructuredAdjustments,
|
|
240
242
|
reverseAdjustmentIds: adminReverseAdjustmentIds,
|
|
243
|
+
preserveExistingReceipt,
|
|
241
244
|
refundDispositionsByOperationId,
|
|
242
245
|
useAdminFeAuthoritativeQuote,
|
|
243
246
|
}), [
|
|
@@ -255,6 +258,7 @@ export function useAdminChangeQuotePreview({
|
|
|
255
258
|
promoApplicationScope,
|
|
256
259
|
adminStructuredAdjustments,
|
|
257
260
|
adminReverseAdjustmentIds,
|
|
261
|
+
preserveExistingReceipt,
|
|
258
262
|
refundDispositionsByOperationId,
|
|
259
263
|
useAdminFeAuthoritativeQuote,
|
|
260
264
|
]);
|
|
@@ -526,6 +530,7 @@ export function useAdminChangeQuotePreview({
|
|
|
526
530
|
promoApplicationScope,
|
|
527
531
|
structuredAdjustments: adminStructuredAdjustments,
|
|
528
532
|
reverseAdjustmentIds: adminReverseAdjustmentIds,
|
|
533
|
+
preserveExistingReceipt,
|
|
529
534
|
includeSelectableAdjustmentComponents:
|
|
530
535
|
needsAdjustmentDiscovery || adminCustomReceiptLines.some(
|
|
531
536
|
(line) => line.scope === 'SELECTED_COMPONENTS',
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -1475,6 +1475,8 @@ export interface ChangeBookingQuoteRequest {
|
|
|
1475
1475
|
}>;
|
|
1476
1476
|
/** Existing immutable adjustment ids to reverse as part of this amendment. */
|
|
1477
1477
|
reverseAdjustmentIds?: string[];
|
|
1478
|
+
/** Admin only: change operations without changing the receipt or outstanding balance. */
|
|
1479
|
+
preserveExistingReceipt?: boolean;
|
|
1478
1480
|
}
|
|
1479
1481
|
|
|
1480
1482
|
/** FE-authored receipt payload for admin quote path (major units, booking currency). */
|
|
@@ -808,6 +808,38 @@ test('admin preview and checkout share the Pricing V2 request shape', () => {
|
|
|
808
808
|
assert.equal('clientProposedTotal' in request, false);
|
|
809
809
|
});
|
|
810
810
|
|
|
811
|
+
test('preserve-receipt quote is a first-class backend mode and excludes price edits', () => {
|
|
812
|
+
const request = buildAdminChangeV2QuoteRequest({
|
|
813
|
+
bookingReference: 'VFSCAEKQ',
|
|
814
|
+
lastName: 'Tauro',
|
|
815
|
+
parentProductId: 'product_1',
|
|
816
|
+
optionId: 'option_2',
|
|
817
|
+
selectedAvailability: {
|
|
818
|
+
availabilityId: 'availability_2',
|
|
819
|
+
productId: 'option_2',
|
|
820
|
+
dateTime: '2026-07-29T08:35:00-06:00',
|
|
821
|
+
vacancies: 10,
|
|
822
|
+
} as Parameters<typeof buildAdminChangeV2QuoteRequest>[0]['selectedAvailability'],
|
|
823
|
+
pickupLocationId: 'pickup_1',
|
|
824
|
+
returnAvailabilityId: null,
|
|
825
|
+
bookingItems: [{ category: 'ADULT', count: 2 }],
|
|
826
|
+
addOnSelections: [],
|
|
827
|
+
cancellationPolicyId: 'policy_1',
|
|
828
|
+
structuredAdjustments: [{ adjustmentId: 'ignored_draft' }],
|
|
829
|
+
reverseAdjustmentIds: ['ignored_reversal'],
|
|
830
|
+
preserveExistingReceipt: true,
|
|
831
|
+
refundDispositionsByOperationId: { ignored_removal: 'PENDING_REFUND' },
|
|
832
|
+
previousPassengerCount: 2,
|
|
833
|
+
previousAvailabilityId: 'availability_1',
|
|
834
|
+
previousReturnAvailabilityId: null,
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
assert.equal(request.preserveExistingReceipt, true);
|
|
838
|
+
assert.deepEqual(request.structuredAdjustments, []);
|
|
839
|
+
assert.deepEqual(request.reverseAdjustmentIds, []);
|
|
840
|
+
assert.equal(request.refundDispositionsByOperationId, undefined);
|
|
841
|
+
});
|
|
842
|
+
|
|
811
843
|
test('admin Pricing V2 quote mapper preserves the server quote identity', async () => {
|
|
812
844
|
const request = buildAdminChangeV2QuoteRequest({
|
|
813
845
|
bookingReference: 'VFSCAEKQ',
|
|
@@ -934,6 +966,49 @@ test('admin no-charge provider payload carries the selected pickup itinerary', (
|
|
|
934
966
|
assert.equal(payload?.promoApplicationScope, null);
|
|
935
967
|
});
|
|
936
968
|
|
|
969
|
+
test('preserve-receipt provider payload carries the mode without price mutations', () => {
|
|
970
|
+
const payload = buildAdminChangeProviderPayload({
|
|
971
|
+
selectedAvailability: {
|
|
972
|
+
availabilityId: 'availability_2',
|
|
973
|
+
productId: 'option_2',
|
|
974
|
+
dateTime: '2026-07-29T08:35:00-06:00',
|
|
975
|
+
vacancies: 10,
|
|
976
|
+
} as Parameters<typeof buildAdminChangeProviderPayload>[0]['selectedAvailability'],
|
|
977
|
+
availabilityProductOptionId: 'option_2',
|
|
978
|
+
bookingItems: [{ category: 'ADULT', count: 2 }],
|
|
979
|
+
product: { productId: 'product_1', name: 'Changed tour' } as Parameters<
|
|
980
|
+
typeof buildAdminChangeProviderPayload
|
|
981
|
+
>[0]['product'],
|
|
982
|
+
pickupLocationId: null,
|
|
983
|
+
selectedReturnOption: null,
|
|
984
|
+
addOnSelections: [],
|
|
985
|
+
cancellationPolicyId: null,
|
|
986
|
+
appliedPromoCode: null,
|
|
987
|
+
newTotalAmount: 999,
|
|
988
|
+
providerPricingOverrides: [{ lineKey: 'ticket', amount: 999 }],
|
|
989
|
+
mergedProviderAdditionalAdjustments: [{ label: 'Draft fee', amount: 10 }],
|
|
990
|
+
adminStructuredAdjustments: [{
|
|
991
|
+
adjustmentId: 'draft',
|
|
992
|
+
mode: 'FIXED_AMOUNT',
|
|
993
|
+
fixedAmount: 10,
|
|
994
|
+
scope: 'CURRENT_ACTIVE_BOOKING',
|
|
995
|
+
taxBehavior: 'NON_TAXABLE',
|
|
996
|
+
reasonCode: 'STAFF_ADJUSTMENT',
|
|
997
|
+
}],
|
|
998
|
+
adminReverseAdjustmentIds: ['old_adjustment'],
|
|
999
|
+
preserveExistingReceipt: true,
|
|
1000
|
+
refundDispositionsByOperationId: { removal: 'PENDING_REFUND' },
|
|
1001
|
+
previousPassengerCount: 2,
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
assert.equal(payload?.preserveExistingReceipt, true);
|
|
1005
|
+
assert.equal(payload?.newTotalAmount, undefined);
|
|
1006
|
+
assert.equal(payload?.pricingAdjustment, undefined);
|
|
1007
|
+
assert.deepEqual(payload?.structuredAdjustments, []);
|
|
1008
|
+
assert.deepEqual(payload?.reverseAdjustmentIds, []);
|
|
1009
|
+
assert.deepEqual(payload?.refundDispositionsByOperationId, {});
|
|
1010
|
+
});
|
|
1011
|
+
|
|
937
1012
|
test('public paid change preview shows amendment-only charges before combined tax', () => {
|
|
938
1013
|
const summary = buildPublicChangeAmountDueSummary([
|
|
939
1014
|
{
|