@ticketboothapp/booking 1.2.132 → 1.2.134

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.132",
3
+ "version": "1.2.134",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -554,6 +554,7 @@ export function AdminChangeBookingFlow({
554
554
  providerPricingOverrides,
555
555
  adminCustomLinesAsAdditionalAdjustments,
556
556
  mergedProviderAdditionalAdjustments,
557
+ adminStructuredAdjustments,
557
558
  unchangedReceiptAnchor,
558
559
  showChangeFlowManualPriceLines,
559
560
  showAdminCustomLineEditor,
@@ -870,6 +871,7 @@ export function AdminChangeBookingFlow({
870
871
  displayChangeFlowProposedTotalWithEditableLines,
871
872
  providerPricingOverrides,
872
873
  mergedProviderAdditionalAdjustments,
874
+ adminStructuredAdjustments,
873
875
  providerApplyAuthoritativeReceipt,
874
876
  onSuccess,
875
877
  totalPrice,
@@ -9,10 +9,17 @@ export type AdminCustomReceiptLine = {
9
9
  label: string;
10
10
  amountInput: string;
11
11
  amountSign: 1 | -1;
12
+ calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
13
+ scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
14
+ taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
15
+ reasonCode: string;
12
16
  };
13
17
 
14
18
  type AdminCustomReceiptLinePatch = Partial<
15
- Pick<AdminCustomReceiptLine, 'label' | 'amountInput' | 'amountSign'>
19
+ Pick<
20
+ AdminCustomReceiptLine,
21
+ 'label' | 'amountInput' | 'amountSign' | 'calculation' | 'scope' | 'taxBehavior' | 'reasonCode'
22
+ >
16
23
  >;
17
24
 
18
25
  export interface AdminChangePricingMessagesProps {
@@ -181,6 +188,19 @@ export function AdminChangePricingAdjustments({
181
188
  onUpdateAdminCustomReceiptLine(adj.id, { label: e.target.value })
182
189
  }
183
190
  />
191
+ <select
192
+ className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
193
+ aria-label="Adjustment calculation"
194
+ value={adj.calculation}
195
+ onChange={(e) =>
196
+ onUpdateAdminCustomReceiptLine(adj.id, {
197
+ calculation: e.target.value as AdminCustomReceiptLine['calculation'],
198
+ })
199
+ }
200
+ >
201
+ <option value="FIXED_AMOUNT">Fixed amount</option>
202
+ <option value="PERCENTAGE">Percentage</option>
203
+ </select>
184
204
  <div
185
205
  className="flex shrink-0 rounded-lg border border-stone-300 bg-stone-100/90 p-0.5 shadow-sm"
186
206
  role="group"
@@ -215,7 +235,7 @@ export function AdminChangePricingAdjustments({
215
235
  type="text"
216
236
  inputMode="decimal"
217
237
  className="admin-custom-receipt-input-amount w-[7rem] shrink-0 rounded-md border border-stone-300 bg-white text-right text-sm font-medium tabular-nums text-stone-800 placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-stone-400/80"
218
- placeholder="0.00"
238
+ placeholder={adj.calculation === 'PERCENTAGE' ? '10' : '0.00'}
219
239
  aria-label="Amount"
220
240
  value={adj.amountInput}
221
241
  onChange={(e) =>
@@ -224,6 +244,38 @@ export function AdminChangePricingAdjustments({
224
244
  })
225
245
  }
226
246
  />
247
+ <span className="text-xs text-stone-500" aria-hidden>
248
+ {adj.calculation === 'PERCENTAGE' ? '%' : ''}
249
+ </span>
250
+ <select
251
+ className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
252
+ aria-label="Adjustment scope"
253
+ value={adj.scope}
254
+ onChange={(e) =>
255
+ onUpdateAdminCustomReceiptLine(adj.id, {
256
+ scope: e.target.value as AdminCustomReceiptLine['scope'],
257
+ })
258
+ }
259
+ >
260
+ <option value="POSITIVE_AMENDMENT_VALUE">Added/change value</option>
261
+ <option value="CURRENT_ACTIVE_TICKETS">All active tickets</option>
262
+ <option value="CURRENT_ACTIVE_BOOKING">Entire active booking</option>
263
+ </select>
264
+ <select
265
+ className="rounded-md border border-stone-300 bg-white px-2 py-1 text-xs text-stone-700"
266
+ aria-label="Tax treatment"
267
+ value={adj.taxBehavior}
268
+ onChange={(e) =>
269
+ onUpdateAdminCustomReceiptLine(adj.id, {
270
+ taxBehavior: e.target.value as AdminCustomReceiptLine['taxBehavior'],
271
+ })
272
+ }
273
+ >
274
+ <option value="NON_TAXABLE">Non-taxable</option>
275
+ <option value="TAXABLE">Taxable</option>
276
+ <option value="PRE_TAX_DISCOUNT">Pre-tax discount</option>
277
+ <option value="POST_TAX_DISCOUNT">Post-tax discount</option>
278
+ </select>
227
279
  <button
228
280
  type="button"
229
281
  className="admin-custom-receipt-remove ml-auto shrink-0 rounded-md text-stone-400 hover:bg-stone-100 hover:text-stone-700 sm:ml-0"
@@ -32,6 +32,7 @@ export interface BuildAdminChangeProviderPayloadParams {
32
32
  newTotalAmount: number;
33
33
  providerPricingOverrides: AdminChangeProviderLineOverride[];
34
34
  mergedProviderAdditionalAdjustments: AdminChangeProviderAdditionalAdjustment[];
35
+ adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
35
36
  providerApplyAuthoritativeReceipt?: ProviderDashboardChangeBookingPayload['authoritativeReceipt'];
36
37
  previousPassengerCount: number;
37
38
  previousAvailabilityId?: string | null;
@@ -52,6 +53,7 @@ export function buildAdminChangeProviderPayload({
52
53
  newTotalAmount,
53
54
  providerPricingOverrides,
54
55
  mergedProviderAdditionalAdjustments,
56
+ adminStructuredAdjustments,
55
57
  providerApplyAuthoritativeReceipt,
56
58
  previousPassengerCount,
57
59
  previousAvailabilityId,
@@ -90,6 +92,7 @@ export function buildAdminChangeProviderPayload({
90
92
  }
91
93
  : undefined,
92
94
  authoritativeReceipt: providerApplyAuthoritativeReceipt,
95
+ structuredAdjustments: adminStructuredAdjustments,
93
96
  capacitySeatCredit: {
94
97
  enabled: true,
95
98
  previousPassengerCount,
@@ -1,5 +1,35 @@
1
1
  import type { Currency } from './CurrencySwitcher';
2
2
 
3
+ export type AdminAmendmentAdjustmentMode =
4
+ | 'FIXED_AMOUNT'
5
+ | 'PERCENTAGE_DISCOUNT'
6
+ | 'PERCENTAGE_SURCHARGE';
7
+
8
+ export type AdminAmendmentAdjustmentScope =
9
+ | 'POSITIVE_AMENDMENT_VALUE'
10
+ | 'CURRENT_ACTIVE_TICKETS'
11
+ | 'CURRENT_ACTIVE_BOOKING'
12
+ | 'SELECTED_COMPONENTS';
13
+
14
+ export type AdminAmendmentTaxBehavior =
15
+ | 'NON_TAXABLE'
16
+ | 'TAXABLE'
17
+ | 'PRE_TAX_DISCOUNT'
18
+ | 'POST_TAX_DISCOUNT';
19
+
20
+ export type AdminAmendmentStructuredAdjustment = {
21
+ adjustmentId: string;
22
+ mode: AdminAmendmentAdjustmentMode;
23
+ fixedAmount?: number | null;
24
+ percentageBasisPoints?: number | null;
25
+ scope: AdminAmendmentAdjustmentScope;
26
+ selectedComponentIds?: string[];
27
+ taxBehavior: AdminAmendmentTaxBehavior;
28
+ reasonCode: string;
29
+ referenceCode?: string | null;
30
+ reason?: string | null;
31
+ };
32
+
3
33
  /** Payload passed to `onChangeBooking` when applying a dashboard-managed booking change. */
4
34
  export type ProviderDashboardChangeBookingPayload = {
5
35
  /** Parent catalog product id. Present when admin changes the booking to a different product. */
@@ -26,6 +56,15 @@ export type ProviderDashboardChangeBookingPayload = {
26
56
  /** Signed major-unit rows; may include admin custom receipt lines merged with provider inline adjustments. */
27
57
  additionalAdjustments?: Array<{ label: string; amount: number }>;
28
58
  } | null;
59
+ /** Server-resolved amendment adjustments. Percentage amounts are never calculated by the browser. */
60
+ structuredAdjustments?: AdminAmendmentStructuredAdjustment[];
61
+ /** Immutable adjustment ids selected for reversal. */
62
+ reverseAdjustmentIds?: string[];
63
+ /** Admin refund decision for removal operations returned by the quote preview. */
64
+ refundDispositionsByOperationId?: Record<
65
+ string,
66
+ 'REFUND_TO_ORIGINAL_PAYMENT' | 'PENDING_REFUND' | 'NO_REFUND'
67
+ >;
29
68
  /**
30
69
  * Admin/provider change flow: when FE-authoritative quoting is used, apply must persist
31
70
  * the exact receipt that was quoted instead of rebuilding from catalog pricing.
@@ -126,6 +126,7 @@ export interface UseAdminChangeCheckoutControllerParams {
126
126
  displayChangeFlowProposedTotalWithEditableLines: number;
127
127
  providerPricingOverrides: AdminChangeProviderLineOverride[];
128
128
  mergedProviderAdditionalAdjustments: AdminChangeProviderAdditionalAdjustment[];
129
+ adminStructuredAdjustments: NonNullable<ProviderDashboardChangeBookingPayload['structuredAdjustments']>;
129
130
  providerApplyAuthoritativeReceipt?: ProviderDashboardChangeBookingPayload['authoritativeReceipt'];
130
131
  onSuccess?: ChangeBookingFlowProps['onSuccess'];
131
132
  totalPrice: number;
@@ -223,6 +224,7 @@ export function useAdminChangeCheckoutController({
223
224
  displayChangeFlowProposedTotalWithEditableLines,
224
225
  providerPricingOverrides,
225
226
  mergedProviderAdditionalAdjustments,
227
+ adminStructuredAdjustments,
226
228
  providerApplyAuthoritativeReceipt,
227
229
  onSuccess,
228
230
  totalPrice,
@@ -320,6 +322,7 @@ export function useAdminChangeCheckoutController({
320
322
  newTotalAmount: displayChangeFlowProposedTotalWithEditableLines,
321
323
  providerPricingOverrides,
322
324
  mergedProviderAdditionalAdjustments,
325
+ adminStructuredAdjustments,
323
326
  providerApplyAuthoritativeReceipt,
324
327
  previousPassengerCount: changeFlowInitialTicketCount,
325
328
  previousAvailabilityId: initialValues?.availabilityId ?? null,
@@ -27,7 +27,12 @@ import { useAdminChangeProtectedPricing } from './useAdminChangeProtectedPricing
27
27
  import { useAdminChangePromoDiscount } from './useAdminChangePromoDiscount';
28
28
 
29
29
  type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
30
- type AdminCustomReceiptLine = { label: string; amountInput: string; amountSign?: number };
30
+ type AdminCustomReceiptLine = {
31
+ label: string;
32
+ amountInput: string;
33
+ amountSign?: number;
34
+ calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
35
+ };
31
36
  type ProviderPricingUi = NonNullable<
32
37
  NonNullable<ChangeBookingFlowProps['flowUi']>['providerDashboardChangePricingUi']
33
38
  >;
@@ -171,6 +176,10 @@ export function useAdminChangePriceSummary({
171
176
  addOns,
172
177
  adminCustomReceiptLines,
173
178
  });
179
+ const promoEligibleSubtotalForCheckoutExcludingCancellation = Math.max(
180
+ 0,
181
+ promoEligibleSubtotalForCheckout - cancellationPolicyFee,
182
+ );
174
183
 
175
184
  const providerPricingUi = flowUi?.providerDashboardChangePricingUi;
176
185
  const providerQuotedLines = useMemo(
@@ -315,7 +324,7 @@ export function useAdminChangePriceSummary({
315
324
  ticketLineItemsForChangeFlowDisplay,
316
325
  feeLineItemsWithAddOns,
317
326
  effectiveSubtotalForCheckout,
318
- promoEligibleSubtotalForCheckout,
327
+ promoEligibleSubtotalForCheckout: promoEligibleSubtotalForCheckoutExcludingCancellation,
319
328
  addOnTotal,
320
329
  originalAddOnSubtotal,
321
330
  originalProductOptionId,
@@ -37,7 +37,7 @@ interface UseAdminChangePromoDiscountParams {
37
37
  function originalReceiptGrossSubtotalBeforePromo(
38
38
  originalReceipt: ChangeBookingFlowProps['originalReceipt'],
39
39
  ): number {
40
- const chargeTypes = new Set(['TICKET', 'FEE', 'RETURN_OPTION', 'CANCELLATION_UPGRADE']);
40
+ const chargeTypes = new Set(['TICKET', 'FEE', 'RETURN_OPTION']);
41
41
  return (originalReceipt?.lineItems ?? []).reduce((sum, line) => {
42
42
  const amount = Number(line.amount) || 0;
43
43
  if (amount <= 0) return sum;
@@ -15,7 +15,12 @@ import type { Currency } from './CurrencySwitcher';
15
15
  import { normalizeLineLabelForCompare } from './change-booking-flow-helpers';
16
16
 
17
17
  type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
18
- type AdminCustomReceiptLine = { label: string; amountInput: string; amountSign?: number };
18
+ type AdminCustomReceiptLine = {
19
+ label: string;
20
+ amountInput: string;
21
+ amountSign?: number;
22
+ calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
23
+ };
19
24
 
20
25
  function addOnSelectionTotal(selections: AddOnSelection[] | null | undefined, addOns: AddOn[]): number {
21
26
  let sum = 0;
@@ -215,6 +220,7 @@ export function useAdminChangeProtectedPricing({
215
220
  () =>
216
221
  roundMoney(
217
222
  adminCustomReceiptLines.reduce((sum, line) => {
223
+ if (line.calculation !== 'FIXED_AMOUNT') return sum;
218
224
  const raw = line.amountInput.trim();
219
225
  if (raw === '' || raw === '+' || raw === '-') return sum;
220
226
  const n = Number(raw);
@@ -336,6 +336,7 @@ export function useAdminChangeQuoteDisplayState({
336
336
  providerPricingOverrides,
337
337
  adminCustomLinesAsAdditionalAdjustments,
338
338
  mergedProviderAdditionalAdjustments,
339
+ adminStructuredAdjustments,
339
340
  } = useAdminProviderPricingAdjustments({
340
341
  isProviderDashboardChange,
341
342
  showProviderPricingInlineEditor,
@@ -425,6 +426,7 @@ export function useAdminChangeQuoteDisplayState({
425
426
  providerPricingOverrides,
426
427
  adminCustomLinesAsAdditionalAdjustments,
427
428
  mergedProviderAdditionalAdjustments,
429
+ adminStructuredAdjustments,
428
430
  unchangedReceiptAnchor,
429
431
  showChangeFlowManualPriceLines,
430
432
  showAdminCustomLineEditor,
@@ -5,10 +5,17 @@ export type AdminCustomReceiptLine = {
5
5
  label: string;
6
6
  amountInput: string;
7
7
  amountSign: 1 | -1;
8
+ calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
9
+ scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
10
+ taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
11
+ reasonCode: string;
8
12
  };
9
13
 
10
14
  type AdminCustomReceiptLinePatch = Partial<
11
- Pick<AdminCustomReceiptLine, 'label' | 'amountInput' | 'amountSign'>
15
+ Pick<
16
+ AdminCustomReceiptLine,
17
+ 'label' | 'amountInput' | 'amountSign' | 'calculation' | 'scope' | 'taxBehavior' | 'reasonCode'
18
+ >
12
19
  >;
13
20
 
14
21
  export function useAdminCustomReceiptLines(resetKey: string | null | undefined) {
@@ -25,6 +32,10 @@ export function useAdminCustomReceiptLines(resetKey: string | null | undefined)
25
32
  label: '',
26
33
  amountInput: '',
27
34
  amountSign: 1,
35
+ calculation: 'FIXED_AMOUNT',
36
+ scope: 'POSITIVE_AMENDMENT_VALUE',
37
+ taxBehavior: 'NON_TAXABLE',
38
+ reasonCode: 'STAFF_ADJUSTMENT',
28
39
  },
29
40
  ]);
30
41
  }, []);
@@ -6,7 +6,19 @@ import type {
6
6
  } from './booking-flow-ui';
7
7
  import type { ProviderDashboardChangeBookingPayload } from './provider-dashboard-change-booking';
8
8
 
9
- type AdminCustomReceiptLine = { label: string; amountInput: string; amountSign?: number };
9
+ type AdminCustomReceiptLine = {
10
+ id: string;
11
+ label: string;
12
+ amountInput: string;
13
+ amountSign?: number;
14
+ calculation: 'FIXED_AMOUNT' | 'PERCENTAGE';
15
+ scope: 'POSITIVE_AMENDMENT_VALUE' | 'CURRENT_ACTIVE_TICKETS' | 'CURRENT_ACTIVE_BOOKING';
16
+ taxBehavior: 'NON_TAXABLE' | 'TAXABLE' | 'PRE_TAX_DISCOUNT' | 'POST_TAX_DISCOUNT';
17
+ reasonCode: string;
18
+ };
19
+ type AdminStructuredAdjustment = NonNullable<
20
+ ProviderDashboardChangeBookingPayload['structuredAdjustments']
21
+ >[number];
10
22
  type ProviderLineOverride = NonNullable<
11
23
  NonNullable<ProviderDashboardChangeBookingPayload['pricingAdjustment']>['lineOverrides']
12
24
  >[number];
@@ -124,9 +136,33 @@ export function useAdminProviderPricingAdjustments({
124
136
  [adminCustomReceiptLines],
125
137
  );
126
138
 
139
+ const adminStructuredAdjustments = useMemo(
140
+ (): AdminStructuredAdjustment[] =>
141
+ adminCustomReceiptLines.flatMap((line) => {
142
+ const value = Number(line.amountInput.trim());
143
+ if (!Number.isFinite(value) || value <= 0) return [];
144
+ const discount = (line.amountSign ?? 1) < 0;
145
+ const percentage = line.calculation === 'PERCENTAGE';
146
+ return [{
147
+ adjustmentId: line.id,
148
+ mode: percentage
149
+ ? discount ? 'PERCENTAGE_DISCOUNT' : 'PERCENTAGE_SURCHARGE'
150
+ : 'FIXED_AMOUNT',
151
+ ...(percentage
152
+ ? { percentageBasisPoints: Math.round(value * 100) }
153
+ : { fixedAmount: roundMoney((discount ? -1 : 1) * value) }),
154
+ scope: line.scope,
155
+ taxBehavior: line.taxBehavior,
156
+ reasonCode: line.reasonCode.trim() || 'STAFF_ADJUSTMENT',
157
+ reason: line.label.trim() || null,
158
+ }];
159
+ }),
160
+ [adminCustomReceiptLines],
161
+ );
162
+
127
163
  const mergedProviderAdditionalAdjustments = useMemo(
128
- () => [...providerAdditionalAdjustments, ...adminCustomLinesAsAdditionalAdjustments],
129
- [providerAdditionalAdjustments, adminCustomLinesAsAdditionalAdjustments],
164
+ () => providerAdditionalAdjustments,
165
+ [providerAdditionalAdjustments],
130
166
  );
131
167
 
132
168
  const hasEffectiveChangeSelection =
@@ -142,5 +178,6 @@ export function useAdminProviderPricingAdjustments({
142
178
  providerPricingOverrides,
143
179
  adminCustomLinesAsAdditionalAdjustments,
144
180
  mergedProviderAdditionalAdjustments,
181
+ adminStructuredAdjustments,
145
182
  };
146
183
  }
@@ -142,6 +142,10 @@ export function useChangeBookingPriceSummary({
142
142
  originalAddOnSelections,
143
143
  addOns,
144
144
  });
145
+ const promoEligibleSubtotalExcludingCancellation = Math.max(
146
+ 0,
147
+ promoEligibleSubtotal - cancellationPolicyFee,
148
+ );
145
149
 
146
150
  const {
147
151
  effectivePromoDiscountAmount,
@@ -159,7 +163,7 @@ export function useChangeBookingPriceSummary({
159
163
  ticketLineItemsForChangeFlowDisplay,
160
164
  feeLineItems,
161
165
  effectiveSubtotal,
162
- promoEligibleSubtotal,
166
+ promoEligibleSubtotal: promoEligibleSubtotalExcludingCancellation,
163
167
  addOnTotal,
164
168
  originalAddOnSubtotal,
165
169
  originalProductOptionId,
@@ -34,7 +34,7 @@ interface UseChangeBookingPromoDiscountParams {
34
34
  function originalReceiptGrossSubtotalBeforePromo(
35
35
  originalReceipt: ChangeBookingFlowProps['originalReceipt'],
36
36
  ): number {
37
- const chargeTypes = new Set(['TICKET', 'FEE', 'RETURN_OPTION', 'CANCELLATION_UPGRADE']);
37
+ const chargeTypes = new Set(['TICKET', 'FEE', 'RETURN_OPTION']);
38
38
  return (originalReceipt?.lineItems ?? []).reduce((sum, line) => {
39
39
  const amount = Number(line.amount) || 0;
40
40
  if (amount <= 0) return sum;
@@ -246,7 +246,7 @@ export function useStandardBookingPriceSummary({
246
246
  const checkoutReturnLineAmount = returnPriceAdjustment;
247
247
  const effectiveSubtotalBeforeAddOns = subtotal;
248
248
  const effectiveSubtotal = effectiveSubtotalBeforeAddOns + addOnTotal;
249
- const promoEligibleSubtotal = effectiveSubtotalBeforeAddOns;
249
+ const promoEligibleSubtotal = Math.max(0, effectiveSubtotalBeforeAddOns - cancellationPolicyFee);
250
250
 
251
251
  const feeLineItemsWithAddOns = useMemo(() => {
252
252
  const addOnLines = addOnSelections
package/src/index.ts CHANGED
@@ -26,6 +26,10 @@ export type {
26
26
  export { PrivateShuttleBookingFlow } from './components/booking/PrivateShuttleBookingFlow';
27
27
  export type { BookingFlowUiOptions } from './components/booking/booking-flow-ui';
28
28
  export type {
29
+ AdminAmendmentAdjustmentMode,
30
+ AdminAmendmentAdjustmentScope,
31
+ AdminAmendmentStructuredAdjustment,
32
+ AdminAmendmentTaxBehavior,
29
33
  ProviderDashboardChangeBookingPayload,
30
34
  ProviderDashboardInitialBooking,
31
35
  } from './components/booking/provider-dashboard-change-booking';