@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.
Files changed (65) hide show
  1. package/package.json +1 -1
  2. package/src/components/booking/AdminChangeBookingContent.tsx +15 -1
  3. package/src/components/booking/AdminChangeBookingFlow.tsx +274 -94
  4. package/src/components/booking/AdminChangeCheckoutPanel.tsx +49 -13
  5. package/src/components/booking/AdminChangePricingPanel.tsx +400 -134
  6. package/src/components/booking/AdminChangePromoEditor.tsx +97 -0
  7. package/src/components/booking/AdminChangeReceiptComparison.tsx +172 -147
  8. package/src/components/booking/ChangeBookingFlow.tsx +6 -3
  9. package/src/components/booking/ChangeBookingQuoteStatusPlaceholder.tsx +7 -2
  10. package/src/components/booking/ChangeBookingSelectionControlsPanel.tsx +6 -0
  11. package/src/components/booking/ChangeBookingTicketsAndAddOnsPanel.tsx +80 -0
  12. package/src/components/booking/NewBookingFlow.tsx +14 -2
  13. package/src/components/booking/PickupLocationSelector.module.css +22 -0
  14. package/src/components/booking/PickupLocationSelector.tsx +3 -3
  15. package/src/components/booking/PriceBreakdown.tsx +66 -15
  16. package/src/components/booking/PriceSummary.tsx +16 -0
  17. package/src/components/booking/PrivateShuttleBookingFlow.tsx +52 -14
  18. package/src/components/booking/PrivateShuttleCheckoutSection.tsx +95 -54
  19. package/src/components/booking/admin-adjustment-components.ts +44 -0
  20. package/src/components/booking/admin-adjustment-tax-behavior.ts +50 -0
  21. package/src/components/booking/admin-change-flow-state-helpers.ts +10 -0
  22. package/src/components/booking/admin-change-provider-payload.ts +12 -1
  23. package/src/components/booking/admin-change-quote-request-key.ts +41 -1
  24. package/src/components/booking/admin-change-receipt-lines.ts +49 -0
  25. package/src/components/booking/admin-change-v2-quote-request.ts +109 -0
  26. package/src/components/booking/admin-refund-decision-context.ts +122 -0
  27. package/src/components/booking/admin-refund-disposition.ts +71 -2
  28. package/src/components/booking/availability-date-selection.ts +16 -0
  29. package/src/components/booking/booking-flow-types.ts +4 -0
  30. package/src/components/booking/booking-flow.css +5 -0
  31. package/src/components/booking/change-booking-error-message.ts +137 -0
  32. package/src/components/booking/change-booking-flow-helpers.ts +77 -2
  33. package/src/components/booking/change-booking-quote-guards.ts +4 -1
  34. package/src/components/booking/change-booking-quote-state.ts +44 -1
  35. package/src/components/booking/incompatible-add-on-selections.ts +19 -0
  36. package/src/components/booking/private-shuttle-availability.ts +14 -0
  37. package/src/components/booking/private-shuttle-cancellation-policy.ts +9 -0
  38. package/src/components/booking/private-shuttle-checkout-controller.ts +5 -0
  39. package/src/components/booking/private-shuttle-provider-change-runner.ts +21 -0
  40. package/src/components/booking/private-shuttle-reservation-runner.ts +5 -3
  41. package/src/components/booking/provider-dashboard-change-booking.ts +18 -2
  42. package/src/components/booking/use-standard-booking-availability.ts +5 -2
  43. package/src/components/booking/useAdminChangeCheckoutController.ts +56 -1
  44. package/src/components/booking/useAdminChangeProductReset.ts +6 -0
  45. package/src/components/booking/useAdminChangeProtectedPricing.ts +4 -0
  46. package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +8 -6
  47. package/src/components/booking/useAdminChangeQuotePreview.ts +146 -61
  48. package/src/components/booking/useAdminCustomReceiptLines.ts +171 -10
  49. package/src/components/booking/useAdminProviderPricingAdjustments.ts +50 -19
  50. package/src/components/booking/useBookingAvailabilityAddOns.ts +26 -4
  51. package/src/components/booking/useBookingPromoAndQuantityController.ts +16 -2
  52. package/src/components/booking/useChangeBookingAddOnFloorController.ts +15 -9
  53. package/src/components/booking/useChangeBookingAutoSelections.ts +60 -17
  54. package/src/components/booking/useChangeBookingInitialHydration.ts +10 -7
  55. package/src/components/booking/useChangeBookingQuotePreview.ts +3 -1
  56. package/src/components/booking/useChangeBookingSelectionDetails.ts +23 -17
  57. package/src/components/booking/useStandardBookingAutoSelections.ts +55 -7
  58. package/src/lib/booking/booking-cutoffs.ts +22 -0
  59. package/src/lib/booking/change-booking-server-preview.ts +92 -5
  60. package/src/lib/booking/change-flow-pricing.ts +12 -0
  61. package/src/lib/booking/i18n/messages/en.json +1 -0
  62. package/src/lib/booking/i18n/messages/fr.json +1 -0
  63. package/src/lib/booking-api.ts +76 -1
  64. package/test/change-booking-helpers.test.ts +1319 -7
  65. package/src/components/booking/useAdminChangePricingDebugPanel.tsx +0 -178
@@ -55,6 +55,28 @@ export function filterAvailabilitiesAfterPublicBookingCutoff<T extends { dateTim
55
55
  );
56
56
  }
57
57
 
58
+ /**
59
+ * Admin surfaces intentionally ignore the public advance-booking cutoff, but an
60
+ * availability whose actual start time has passed is no longer bookable by the
61
+ * API. Keep that narrower rule separate so admin and public behavior cannot
62
+ * accidentally inherit one another's cutoff policy.
63
+ */
64
+ export function isAvailabilityAfterCurrentTime(
65
+ availability: { dateTime: string },
66
+ now: Date = new Date(),
67
+ ): boolean {
68
+ return isAvailabilityAfterPublicBookingCutoff(availability, now, 0);
69
+ }
70
+
71
+ export function filterAvailabilitiesAfterCurrentTime<T extends { dateTime: string }>(
72
+ availabilities: T[],
73
+ now: Date = new Date(),
74
+ ): T[] {
75
+ return availabilities.filter((availability) =>
76
+ isAvailabilityAfterCurrentTime(availability, now),
77
+ );
78
+ }
79
+
58
80
  export function isDateWithinPublicPickupUnknownCutoff(date: Date, now: Date = new Date()): boolean {
59
81
  return date.getTime() <= now.getTime() + PUBLIC_PICKUP_UNKNOWN_CUTOFF_MS;
60
82
  }
@@ -4,7 +4,10 @@
4
4
  *
5
5
  * See `CHANGE_BOOKING_BE_HANDOFF.md` in this package for fields the API should populate.
6
6
  */
7
- import type { ChangeBookingQuoteResponse } from '../booking-api';
7
+ import type {
8
+ AdminAmendmentOperationSnapshot,
9
+ ChangeBookingQuoteResponse,
10
+ } from '../booking-api';
8
11
  import type { PricingV2QuoteLineSnapshot } from '../booking-api';
9
12
  import type { PriceBasisSnapshot } from '../booking-api';
10
13
  import type { PriceSummaryLine } from '../../components/booking/PriceSummary';
@@ -24,6 +27,8 @@ export interface ChangeBookingServerPreview {
24
27
  completeness: ChangeBookingPreviewCompleteness;
25
28
  /** Rows for {@link PriceSummary} / checkout — from quote receipt line items + BE extensions. */
26
29
  priceSummaryLines: PriceSummaryLine[];
30
+ /** Full resulting receipt rows, when the amendment quote supplies them. */
31
+ resultingReceiptLines?: PriceSummaryLine[];
27
32
  /** From quote — per-category unit prices (major units) for ticket picker when BE sends them. */
28
33
  ticketUnitPriceByCategory?: Record<string, number>;
29
34
  cancellationPolicyFeeByPolicyId?: Record<string, number>;
@@ -207,6 +212,7 @@ export function mapQuoteLineItemsToPriceSummaryLines(
207
212
  : type;
208
213
  out.push({
209
214
  kind: 'ticket',
215
+ operationId: isPricingV2Line(item) ? item.metadata?.operationId : undefined,
210
216
  category,
211
217
  qty: qty > 0 ? qty : 1,
212
218
  itemTotal: amount,
@@ -228,6 +234,7 @@ export function mapQuoteLineItemsToPriceSummaryLines(
228
234
  ) {
229
235
  out.push({
230
236
  kind: 'ticket',
237
+ operationId: isPricingV2Line(item) ? item.metadata?.operationId : undefined,
231
238
  category: lettersOnly,
232
239
  qty,
233
240
  itemTotal: amount,
@@ -249,6 +256,74 @@ export function mapQuoteLineItemsToPriceSummaryLines(
249
256
  return out;
250
257
  }
251
258
 
259
+ /** Clarify ticket deltas caused by moving a booking without changing receipt semantics. */
260
+ export function labelAdminAmendmentPriceSummaryLines(
261
+ lines: PriceSummaryLine[],
262
+ operations: readonly AdminAmendmentOperationSnapshot[],
263
+ originalLines: readonly PriceSummaryLine[] = [],
264
+ ): PriceSummaryLine[] {
265
+ const movedServiceOperations = new Map(
266
+ operations
267
+ .filter((operation) => operation.type.trim().toUpperCase() === 'MOVE_SERVICE')
268
+ .map((operation) => [operation.operationId, operation] as const),
269
+ );
270
+ if (movedServiceOperations.size === 0) return lines;
271
+
272
+ const normalizedTicketCategory = (category: string) => category
273
+ .replace(/^refund\s*[·:-]\s*/i, '')
274
+ .replace(/\s*[—-]\s*(?:new date|tour) price difference\s*$/i, '')
275
+ .trim()
276
+ .toUpperCase();
277
+
278
+ return lines.map((line) => {
279
+ const moveOperation = line.kind === 'ticket' && line.operationId
280
+ ? movedServiceOperations.get(line.operationId)
281
+ : undefined;
282
+ if (
283
+ line.kind !== 'ticket' ||
284
+ !moveOperation ||
285
+ /(?:new date|tour) price difference/i.test(line.category)
286
+ ) {
287
+ return line;
288
+ }
289
+ const changesTour = moveOperation.changedFields?.some(
290
+ (field) => field.trim().toLowerCase() === 'productid',
291
+ ) ?? false;
292
+ const priceDifferenceLabel = changesTour
293
+ ? 'tour price difference'
294
+ : 'new date price difference';
295
+ const originalLine = originalLines.find(
296
+ (candidate) => candidate.kind === 'ticket' &&
297
+ normalizedTicketCategory(candidate.category) === normalizedTicketCategory(line.category),
298
+ );
299
+ const previousUnitAmount = originalLine?.kind === 'ticket' && originalLine.qty > 0
300
+ ? roundMoney(originalLine.itemTotal / originalLine.qty)
301
+ : null;
302
+ const differenceUnitAmount = line.qty > 0
303
+ ? roundMoney(line.itemTotal / line.qty)
304
+ : null;
305
+ const ticketLabel = normalizedTicketCategory(line.category).toLowerCase();
306
+ return {
307
+ ...line,
308
+ category: `${line.category} — ${priceDifferenceLabel}`,
309
+ ...(previousUnitAmount != null && differenceUnitAmount != null
310
+ ? {
311
+ unitPriceComparison: {
312
+ previousLabel: `Existing ${ticketLabel} price`,
313
+ updatedLabel: changesTour
314
+ ? `New-tour ${ticketLabel} price`
315
+ : `New-date ${ticketLabel} price`,
316
+ differenceLabel: `Difference per ${ticketLabel}`,
317
+ previousUnitAmount,
318
+ updatedUnitAmount: roundMoney(previousUnitAmount + differenceUnitAmount),
319
+ differenceUnitAmount,
320
+ },
321
+ }
322
+ : {}),
323
+ };
324
+ });
325
+ }
326
+
252
327
  function signedAmountDueFromQuote(quote: ChangeBookingQuoteResponse): number {
253
328
  const pricingQuote = quote.pricingQuote ?? quote.quote;
254
329
  // Prefer explicit settlement fields over balanceDelta. Receipt-total math and balanceDelta
@@ -280,10 +355,21 @@ export function buildChangeBookingServerPreview(
280
355
  const paymentCreditTotal = pricingQuote?.paymentCreditTotal ?? quote.paymentCreditTotal;
281
356
 
282
357
  const rawLines = lineItemsForSummary(quote);
283
- const priceSummaryLines =
284
- rawLines && rawLines.length > 0
285
- ? withPaymentCreditSummaryLine(mapQuoteLineItemsToPriceSummaryLines(rawLines), paymentCreditTotal)
286
- : withPaymentCreditSummaryLine([], paymentCreditTotal);
358
+ const mappedLines = rawLines && rawLines.length > 0
359
+ ? mapQuoteLineItemsToPriceSummaryLines(rawLines)
360
+ : [];
361
+ const priceSummaryLines = withPaymentCreditSummaryLine(
362
+ labelAdminAmendmentPriceSummaryLines(
363
+ mappedLines,
364
+ pricingQuote?.amendment?.operations ?? [],
365
+ mapQuoteLineItemsToPriceSummaryLines(quote.originalReceipt?.lineItems),
366
+ ),
367
+ paymentCreditTotal,
368
+ );
369
+ const resultingReceiptLines = pricingQuote?.amendment?.updatedReceipt?.lineItems;
370
+ const resultingReceiptPriceSummaryLines = resultingReceiptLines?.length
371
+ ? mapQuoteLineItemsToPriceSummaryLines(resultingReceiptLines)
372
+ : undefined;
287
373
 
288
374
  const completeness: ChangeBookingPreviewCompleteness = priceSummaryLines.length > 0 ? 'full' : 'totals_only';
289
375
 
@@ -295,6 +381,7 @@ export function buildChangeBookingServerPreview(
295
381
  totalNewBooking: totals.total,
296
382
  completeness,
297
383
  priceSummaryLines,
384
+ resultingReceiptLines: resultingReceiptPriceSummaryLines,
298
385
  ticketUnitPriceByCategory: quote.ticketUnitPriceByCategory,
299
386
  cancellationPolicyFeeByPolicyId: quote.cancellationPolicyFeeByPolicyId,
300
387
  returnOptionPriceByReturnAvailabilityId: quote.returnOptionPriceByReturnAvailabilityId,
@@ -255,6 +255,12 @@ export interface ChangeQuoteUiSlice {
255
255
  quoteExpiresAt?: string;
256
256
  pricingVersion?: string;
257
257
  quotedTotal?: number;
258
+ /** Settled cash after completed refunds, calculated by the backend from payment records. */
259
+ amountPreviouslyPaid?: number;
260
+ /** Non-cash value allocated before and after this amendment. */
261
+ currentPaymentCreditTotal?: number;
262
+ projectedPaymentCreditTotal?: number;
263
+ refundCandidate?: number;
258
264
  paymentCreditTotal?: number;
259
265
  refundDecisionOperations?: ChangeBookingQuoteResponse['refundDecisionOperations'];
260
266
  returnPriceTreatmentOperations?: ChangeBookingQuoteResponse['returnPriceTreatmentOperations'];
@@ -296,6 +302,12 @@ export function sliceChangeQuoteForUi(
296
302
  quoteExpiresAt: pricingQuote?.expiresAt ?? quote.expiresAt,
297
303
  pricingVersion: pricingQuote?.pricingVersion ?? undefined,
298
304
  quotedTotal: pricingQuote?.payableTotal ?? pricingQuote?.totalAmount ?? quote.proposed?.total ?? quote.newReceipt?.total,
305
+ amountPreviouslyPaid:
306
+ pricingQuote?.amountPreviouslyPaid ?? quote.amountPreviouslyPaid ?? undefined,
307
+ currentPaymentCreditTotal: quote.originalReceipt?.paymentCreditTotal ?? 0,
308
+ projectedPaymentCreditTotal:
309
+ pricingQuote?.paymentCreditTotal ?? quote.paymentCreditTotal ?? 0,
310
+ refundCandidate: quote.refundCandidate ?? pricingQuote?.refundCandidate ?? 0,
299
311
  paymentCreditTotal: pricingQuote?.paymentCreditTotal ?? quote.paymentCreditTotal,
300
312
  refundDecisionOperations: quote.refundDecisionOperations,
301
313
  returnPriceTreatmentOperations: quote.returnPriceTreatmentOperations,
@@ -119,6 +119,7 @@
119
119
  "rounding": "Rounding",
120
120
  "deposit": "Deposit",
121
121
  "totalOwedForBookingChange": "Total owed for booking difference",
122
+ "changeToBookingTotal": "Change to booking total",
122
123
  "subtotal": "Subtotal",
123
124
  "tax": "Taxes and fees",
124
125
  "returnOption": "Return Option",
@@ -119,6 +119,7 @@
119
119
  "rounding": "Arrondi",
120
120
  "deposit": "Acompte",
121
121
  "totalOwedForBookingChange": "Montant dû pour le changement de réservation",
122
+ "changeToBookingTotal": "Modification du total de la réservation",
122
123
  "subtotal": "Sous-total",
123
124
  "tax": "Taxes et frais",
124
125
  "returnOption": "Option de retour",
@@ -1102,7 +1102,7 @@ export async function getPromoDiscount(
1102
1102
 
1103
1103
  export async function getAddOns(
1104
1104
  companyId: string,
1105
- options?: { productOptionId?: string; preCheckout?: boolean }
1105
+ options?: { productOptionId?: string; preCheckout?: boolean; dateTime?: string }
1106
1106
  ): Promise<AddOn[]> {
1107
1107
  const params = new URLSearchParams({ companyId });
1108
1108
  if (options?.productOptionId) {
@@ -1110,6 +1110,7 @@ export async function getAddOns(
1110
1110
  if (po) params.set('productOptionId', po);
1111
1111
  }
1112
1112
  if (options?.preCheckout !== undefined) params.set('preCheckout', String(options.preCheckout));
1113
+ if (options?.dateTime?.trim()) params.set('dateTime', options.dateTime.trim());
1113
1114
  const res = await fetchBookingGetWithRetry(`${API_BASE}/1/add-ons?${params}`);
1114
1115
  if (!res.ok) {
1115
1116
  const err = await res.json();
@@ -1251,6 +1252,8 @@ export type AdminAmendmentRefundDisposition =
1251
1252
  | 'PENDING_REFUND'
1252
1253
  | 'NO_REFUND';
1253
1254
 
1255
+ export type AdminPromoApplicationScope = 'CHANGE_ONLY' | 'ENTIRE_BOOKING';
1256
+
1254
1257
  export interface AdminAmendmentOperationSnapshot {
1255
1258
  operationId: string;
1256
1259
  type: string;
@@ -1262,6 +1265,27 @@ export interface AdminAmendmentOperationSnapshot {
1262
1265
  metadata?: Record<string, string>;
1263
1266
  }
1264
1267
 
1268
+ export interface AdminAmendmentActiveAdjustmentSnapshot {
1269
+ adjustmentId: string;
1270
+ operationId: string;
1271
+ mode: 'FIXED_AMOUNT' | 'PERCENTAGE_DISCOUNT' | 'PERCENTAGE_SURCHARGE';
1272
+ scope:
1273
+ | 'POSITIVE_AMENDMENT_VALUE'
1274
+ | 'CURRENT_ACTIVE_TICKETS'
1275
+ | 'CURRENT_ACTIVE_BOOKING'
1276
+ | 'SELECTED_COMPONENTS';
1277
+ basisCents: number;
1278
+ percentageBasisPoints?: number | null;
1279
+ amountCents: number;
1280
+ taxDeltaCents: number;
1281
+ affectedComponentIds: string[];
1282
+ taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
1283
+ reasonCode: string;
1284
+ referenceCode?: string | null;
1285
+ reason?: string | null;
1286
+ createdAt: string;
1287
+ }
1288
+
1265
1289
  export interface ChangeBookingQuoteRequest {
1266
1290
  bookingReference: string;
1267
1291
  lastName: string;
@@ -1276,6 +1300,9 @@ export interface ChangeBookingQuoteRequest {
1276
1300
  newReturnAvailabilityId?: string | null;
1277
1301
  newPassengerCounts?: Array<{ category: string; count: number }>;
1278
1302
  newAddOnSelections?: Array<{ addOnId: string; variantId?: string; quantity?: number }>;
1303
+ /** Explicit promo intent for this amendment. Historical booking promos are never implied. */
1304
+ promoCode?: string | null;
1305
+ promoApplicationScope?: AdminPromoApplicationScope | null;
1279
1306
  /** Full new-booking total shown in the UI; server verifies within tolerance then uses this for the session so charge matches screen. */
1280
1307
  clientProposedTotal?: number;
1281
1308
  /**
@@ -1293,6 +1320,25 @@ export interface ChangeBookingQuoteRequest {
1293
1320
  } | null;
1294
1321
  /** Explicit admin treatment for server-classified removed paid value. Never defaulted by the client. */
1295
1322
  refundDispositionsByOperationId?: Record<string, AdminAmendmentRefundDisposition>;
1323
+ /** Server-resolved admin adjustments to append to the immutable amendment ledger. */
1324
+ structuredAdjustments?: Array<{
1325
+ adjustmentId: string;
1326
+ mode: 'FIXED_AMOUNT' | 'PERCENTAGE_DISCOUNT' | 'PERCENTAGE_SURCHARGE';
1327
+ fixedAmount?: number | null;
1328
+ percentageBasisPoints?: number | null;
1329
+ scope:
1330
+ | 'POSITIVE_AMENDMENT_VALUE'
1331
+ | 'CURRENT_ACTIVE_TICKETS'
1332
+ | 'CURRENT_ACTIVE_BOOKING'
1333
+ | 'SELECTED_COMPONENTS';
1334
+ selectedComponentIds?: string[];
1335
+ taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
1336
+ reasonCode: string;
1337
+ referenceCode?: string | null;
1338
+ reason?: string | null;
1339
+ }>;
1340
+ /** Existing immutable adjustment ids to reverse as part of this amendment. */
1341
+ reverseAdjustmentIds?: string[];
1296
1342
  }
1297
1343
 
1298
1344
  /** FE-authored receipt payload for admin quote path (major units, booking currency). */
@@ -1323,6 +1369,7 @@ export interface AdminChangeBookingQuoteV2Data {
1323
1369
  oldReceipt: {
1324
1370
  currency?: string | null;
1325
1371
  grossSubtotal?: number | null;
1372
+ paymentCreditTotal?: number | null;
1326
1373
  taxAmount?: number | null;
1327
1374
  payableTotal?: number | null;
1328
1375
  lines?: PricingV2QuoteLineSnapshot[] | null;
@@ -1342,6 +1389,7 @@ export interface AdminChangeBookingQuoteV2Data {
1342
1389
  returnPriceTreatmentOperations?: AdminAmendmentOperationSnapshot[];
1343
1390
  allowedRefundDispositionsByOperationId?: Record<string, AdminAmendmentRefundDisposition[]>;
1344
1391
  removedValueByOperationId?: Record<string, number>;
1392
+ reversibleAdjustments?: AdminAmendmentActiveAdjustmentSnapshot[];
1345
1393
  }
1346
1394
 
1347
1395
  export interface ChangeBookingQuoteReceipt {
@@ -1349,11 +1397,18 @@ export interface ChangeBookingQuoteReceipt {
1349
1397
  tax?: number;
1350
1398
  total: number;
1351
1399
  currency?: string;
1400
+ /** Non-cash value currently allocated to the booking, such as gift-card credit. */
1401
+ paymentCreditTotal?: number;
1352
1402
  lineItems?: Array<{
1353
1403
  label?: string;
1354
1404
  amount?: number;
1355
1405
  type?: string;
1356
1406
  quantity?: number;
1407
+ reference?: string | null;
1408
+ identity?: {
1409
+ componentId?: string | null;
1410
+ } | null;
1411
+ priceBasis?: PriceBasisSnapshot | null;
1357
1412
  }>;
1358
1413
  }
1359
1414
 
@@ -1446,6 +1501,7 @@ export interface ChangeBookingQuoteResponse {
1446
1501
  quote?: PricingV2QuoteSnapshot | null;
1447
1502
  paymentCreditTotal?: number;
1448
1503
  balanceDelta?: number;
1504
+ amountPreviouslyPaid?: number;
1449
1505
  amountToCharge?: number;
1450
1506
  refundCandidate?: number;
1451
1507
  /**
@@ -1489,6 +1545,8 @@ export interface ChangeBookingQuoteResponse {
1489
1545
  allowedRefundDispositionsByOperationId?: Record<string, AdminAmendmentRefundDisposition[]>;
1490
1546
  /** Original paid basis plus allocated refundable tax for each removal operation. */
1491
1547
  removedValueByOperationId?: Record<string, number>;
1548
+ /** Active immutable adjustments that an admin may edit (reverse + replace) or remove. */
1549
+ reversibleAdjustments?: AdminAmendmentActiveAdjustmentSnapshot[];
1492
1550
  /** When price check fails / disagrees: optional breakdown for UI line-vs-line comparison. */
1493
1551
  pricingDriftDetail?: ChangeBookingQuotePricingDriftDetail;
1494
1552
  /** Optional BE debug: receipt-floor vs catalog ticket math (same-parent Rule A/B). */
@@ -1683,6 +1741,7 @@ export function mapAdminChangeBookingQuoteV2Data(
1683
1741
  pricingQuote: newQuote,
1684
1742
  quote: newQuote,
1685
1743
  balanceDelta: data.balanceDelta ?? newQuote?.balanceDelta ?? undefined,
1744
+ amountPreviouslyPaid: newQuote?.amountPreviouslyPaid ?? undefined,
1686
1745
  amountToCharge: data.amountToCharge ?? newQuote?.amountToCharge ?? undefined,
1687
1746
  refundCandidate: data.refundCandidate ?? newQuote?.refundCandidate ?? undefined,
1688
1747
  priceDiff: data.balanceDelta ?? newQuote?.balanceDelta ?? 0,
@@ -1694,16 +1753,19 @@ export function mapAdminChangeBookingQuoteV2Data(
1694
1753
  allowedRefundDispositionsByOperationId:
1695
1754
  data.allowedRefundDispositionsByOperationId ?? {},
1696
1755
  removedValueByOperationId: data.removedValueByOperationId ?? {},
1756
+ reversibleAdjustments: data.reversibleAdjustments ?? [],
1697
1757
  originalReceipt: {
1698
1758
  subtotal: oldReceipt.grossSubtotal ?? undefined,
1699
1759
  tax: oldReceipt.taxAmount ?? undefined,
1700
1760
  total: oldReceipt.payableTotal ?? 0,
1701
1761
  currency: oldReceipt.currency ?? undefined,
1762
+ paymentCreditTotal: oldReceipt.paymentCreditTotal ?? 0,
1702
1763
  lineItems: oldReceipt.lines?.map((line) => ({
1703
1764
  label: line.label ?? undefined,
1704
1765
  amount: line.amount ?? undefined,
1705
1766
  type: line.type ?? undefined,
1706
1767
  quantity: line.quantity ?? undefined,
1768
+ priceBasis: line.priceBasis ?? undefined,
1707
1769
  })),
1708
1770
  },
1709
1771
  };
@@ -2239,6 +2301,19 @@ export interface PricingV2QuoteSnapshot {
2239
2301
  bookingReference?: string | null;
2240
2302
  reservationReference?: string | null;
2241
2303
  relatedChangeIntentId?: string | null;
2304
+ amendment?: {
2305
+ operations?: AdminAmendmentOperationSnapshot[] | null;
2306
+ updatedReceipt?: ChangeBookingQuoteReceipt | null;
2307
+ targetState?: {
2308
+ nonPriceState?: {
2309
+ /**
2310
+ * The amendment service stores the canonical itinerary as JSON in non-price state.
2311
+ * Accept an array as well so clients remain compatible if the API stops stringifying it.
2312
+ */
2313
+ itinerary?: string | ItineraryDisplayStep[] | null;
2314
+ } | null;
2315
+ } | null;
2316
+ } | null;
2242
2317
  metadata?: Record<string, string> | null;
2243
2318
  [key: string]: unknown;
2244
2319
  }