@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticketboothapp/booking",
3
- "version": "1.2.186",
3
+ "version": "1.2.187",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -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
- <span className="shrink-0 text-xs font-medium text-stone-500">
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
- </span>
398
+ </span>
399
+ </div>
377
400
  </div>
378
- {promoEditor}
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
- providerPricingOverrides.length > 0 || mergedProviderAdditionalAdjustments.length > 0
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',
@@ -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
  {