@ticketboothapp/booking 1.2.97 → 1.2.99

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.97",
3
+ "version": "1.2.99",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -153,6 +153,49 @@ function omitZeroAmountPromoDiscountSummaryLines(lines: PriceSummaryLine[]): Pri
153
153
  });
154
154
  }
155
155
 
156
+ function isPromoDiscountGiftCardOrVoucherLine(line: {
157
+ type?: string | null;
158
+ label?: string | null;
159
+ amount?: number | null;
160
+ }): boolean {
161
+ const type = String(line.type ?? '').trim().toUpperCase();
162
+ if (type === 'PROMO_CODE' || type === 'DISCOUNT' || type === 'GIFT_CARD' || type === 'VOUCHER') {
163
+ return true;
164
+ }
165
+ const label = String(line.label ?? '').trim().toLowerCase();
166
+ return /promo|discount|voucher|gift\s*card/.test(label);
167
+ }
168
+
169
+ function deriveOriginalReceiptPromoAdjustment(originalReceipt: ChangeBookingFlowProps['originalReceipt']): {
170
+ amount: number;
171
+ label: string | null;
172
+ type: 'PROMO_CODE' | 'GIFT_CARD' | 'VOUCHER' | 'DISCOUNT';
173
+ } {
174
+ let amount = Math.max(0, Number(originalReceipt?.promoAmount) || 0);
175
+ let label = originalReceipt?.promoLabel?.trim() || null;
176
+ let type: 'PROMO_CODE' | 'GIFT_CARD' | 'VOUCHER' | 'DISCOUNT' = 'PROMO_CODE';
177
+
178
+ for (const line of originalReceipt?.lineItems ?? []) {
179
+ if (!isPromoDiscountGiftCardOrVoucherLine(line)) continue;
180
+ const lineAmount = Number(line.amount) || 0;
181
+ if (lineAmount < -0.005) {
182
+ amount = Math.max(amount, Math.abs(lineAmount));
183
+ if (!label) label = line.label?.trim() || null;
184
+ }
185
+ const lineType = String(line.type ?? '').trim().toUpperCase();
186
+ const lineLabel = String(line.label ?? '').trim().toLowerCase();
187
+ if (lineType === 'GIFT_CARD' || /gift\s*card/.test(lineLabel)) {
188
+ type = 'GIFT_CARD';
189
+ } else if (lineType === 'VOUCHER' || /voucher/.test(lineLabel)) {
190
+ type = 'VOUCHER';
191
+ } else if (lineType === 'DISCOUNT' || /discount/.test(lineLabel)) {
192
+ type = 'DISCOUNT';
193
+ }
194
+ }
195
+
196
+ return { amount: roundMoney(amount), label, type };
197
+ }
198
+
156
199
  function resolveTicketQtyFromQuantities(
157
200
  category: string,
158
201
  quantities: Record<string, number>,
@@ -3265,49 +3308,6 @@ export function AdminChangeBookingFlow({
3265
3308
  providerEditableLineByNormalizedLabel,
3266
3309
  ]);
3267
3310
 
3268
- const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
3269
- let raw: PriceSummaryLine[];
3270
- if (suppressSelfServeCurrencyUi && selfServePricingConfirmed) {
3271
- const serverLines = latestChangeQuote?.serverPreview?.priceSummaryLines;
3272
- raw = serverLines && serverLines.length > 0 ? serverLines : checkoutPriceSummaryLines;
3273
- } else {
3274
- raw = checkoutPriceSummaryLines;
3275
- }
3276
- return omitZeroAmountPromoDiscountSummaryLines(raw);
3277
- }, [
3278
- suppressSelfServeCurrencyUi,
3279
- selfServePricingConfirmed,
3280
- checkoutPriceSummaryLines,
3281
- latestChangeQuote?.serverPreview?.priceSummaryLines,
3282
- ]);
3283
-
3284
- /** Receipt/server lines already include {@link PriceSummary}'s TAX row — do not also pass `taxAmount` or it duplicates. */
3285
- const priceSummaryLinesIncludeTaxRow = useMemo(
3286
- () =>
3287
- checkoutPriceSummaryLinesForCheckout.some(
3288
- (line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
3289
- ),
3290
- [checkoutPriceSummaryLinesForCheckout],
3291
- );
3292
- const {
3293
- editableCheckoutPriceSummaryLines,
3294
- editableSummaryLineAmountInputs,
3295
- editableSummaryLineLabelInputs,
3296
- editableSummaryPreSubtotalDelta,
3297
- editableSummaryPreSubtotalDebugRows,
3298
- onLineAmountInputChange: handleEditableLineAmountInputChange,
3299
- onLineAmountInputBlur: handleEditableLineAmountInputBlur,
3300
- onLineAmountReset: handleEditableLineAmountReset,
3301
- } = useEditableSummaryLines(
3302
- checkoutPriceSummaryLinesForCheckout,
3303
- {
3304
- enabled: showProviderPricingInlineEditor,
3305
- onChange: providerPricingUi?.onLineAmountInputChange,
3306
- onBlur: providerPricingUi?.onLineAmountInputBlur,
3307
- onReset: providerPricingUi?.onLineAmountReset,
3308
- },
3309
- );
3310
-
3311
3311
  // Promo discount from backend (order-level only; rates are pre-promo)
3312
3312
  const [promoDiscountAmount, setPromoDiscountAmount] = useState(0);
3313
3313
  const [isGiftCard, setIsGiftCard] = useState(false);
@@ -3323,6 +3323,11 @@ export function AdminChangeBookingFlow({
3323
3323
  changeBookingPromo: null as { priorAmount?: number } | null,
3324
3324
  });
3325
3325
 
3326
+ const originalReceiptPromoAdjustment = useMemo(
3327
+ () => deriveOriginalReceiptPromoAdjustment(originalReceipt),
3328
+ [originalReceipt],
3329
+ );
3330
+
3326
3331
  const promoDiscountFetchKey = useMemo(() => {
3327
3332
  if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
3328
3333
  const companyId = product.companyId ?? env.COMPANY_ID;
@@ -3339,7 +3344,7 @@ export function AdminChangeBookingFlow({
3339
3344
  currency,
3340
3345
  quantitiesSignature,
3341
3346
  String(Math.round(effectiveSubtotalForCheckout * 100)),
3342
- lockedPromoCode ? String(Math.round((originalReceipt?.promoAmount ?? 0) * 100)) : '',
3347
+ lockedPromoCode ? String(Math.round(originalReceiptPromoAdjustment.amount * 100)) : '',
3343
3348
  ].join('::');
3344
3349
  }, [
3345
3350
  appliedPromoCode,
@@ -3353,7 +3358,7 @@ export function AdminChangeBookingFlow({
3353
3358
  quantitiesSignature,
3354
3359
  effectiveSubtotalForCheckout,
3355
3360
  lockedPromoCode,
3356
- originalReceipt?.promoAmount,
3361
+ originalReceiptPromoAdjustment.amount,
3357
3362
  ]);
3358
3363
 
3359
3364
  promoDiscountParamsRef.current = {
@@ -3362,7 +3367,7 @@ export function AdminChangeBookingFlow({
3362
3367
  effectiveSubtotal: effectiveSubtotalForCheckout,
3363
3368
  appliedPromoCode,
3364
3369
  changeBookingPromo: lockedPromoCode
3365
- ? { priorAmount: originalReceipt?.promoAmount }
3370
+ ? { priorAmount: originalReceiptPromoAdjustment.amount }
3366
3371
  : null,
3367
3372
  };
3368
3373
 
@@ -3434,18 +3439,87 @@ export function AdminChangeBookingFlow({
3434
3439
  // Percentage/fixed promos: tax on discounted amount (promo before GST per CRA guidance).
3435
3440
  // Vouchers and gift cards: tax on full subtotal (voucher discount includes tax on free portion; gift card is payment).
3436
3441
  // Change booking: get-promo-discount uses forBookingChange + prior promo from receipt so expired codes stay locked (BE).
3437
- const lockedPromoFallbackAmount =
3438
- lockedPromoCode
3439
- ? Math.max(0, originalReceipt?.promoAmount ?? 0)
3440
- : 0;
3442
+ const receiptPromoFallbackAmount = Math.max(0, originalReceiptPromoAdjustment.amount);
3441
3443
  const effectivePromoDiscountAmount =
3442
- promoDiscountAmount > 0 ? promoDiscountAmount : lockedPromoFallbackAmount;
3444
+ promoDiscountAmount > 0 ? promoDiscountAmount : receiptPromoFallbackAmount;
3445
+ const effectivePromoLineType =
3446
+ isGiftCard || originalReceiptPromoAdjustment.type === 'GIFT_CARD'
3447
+ ? 'GIFT_CARD'
3448
+ : isVoucher || originalReceiptPromoAdjustment.type === 'VOUCHER'
3449
+ ? 'VOUCHER'
3450
+ : originalReceiptPromoAdjustment.type;
3443
3451
  const taxOnSubtotal = isTaxIncludedInPrice ? 0 : effectiveSubtotalForCheckout * (pricingConfig?.taxRate ?? 0);
3444
3452
  const effectiveTax =
3445
- effectivePromoDiscountAmount > 0 && !isGiftCard && !isVoucher
3453
+ effectivePromoDiscountAmount > 0 && effectivePromoLineType !== 'GIFT_CARD' && effectivePromoLineType !== 'VOUCHER'
3446
3454
  ? (effectiveSubtotalForCheckout - effectivePromoDiscountAmount) * (pricingConfig?.taxRate ?? 0)
3447
3455
  : taxOnSubtotal;
3448
3456
  const totalPrice = effectiveSubtotalForCheckout + effectiveTax - effectivePromoDiscountAmount;
3457
+
3458
+ const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
3459
+ let raw: PriceSummaryLine[];
3460
+ if (suppressSelfServeCurrencyUi && selfServePricingConfirmed) {
3461
+ const serverLines = latestChangeQuote?.serverPreview?.priceSummaryLines;
3462
+ raw = serverLines && serverLines.length > 0 ? serverLines : checkoutPriceSummaryLines;
3463
+ } else {
3464
+ raw = checkoutPriceSummaryLines;
3465
+ }
3466
+ const lines = omitZeroAmountPromoDiscountSummaryLines(raw);
3467
+ const hasPromoSummaryLine = lines.some((line) =>
3468
+ line.kind === 'line' && isPromoDiscountGiftCardOrVoucherLine(line)
3469
+ );
3470
+ if (hasPromoSummaryLine || effectivePromoDiscountAmount < 0.005) return lines;
3471
+ const trimmedPromoCode = appliedPromoCode?.trim() ?? '';
3472
+ const promoLabel =
3473
+ trimmedPromoCode.length > 0
3474
+ ? `Promo: ${trimmedPromoCode}`
3475
+ : originalReceiptPromoAdjustment.label || (t('booking.discount') || 'Discount');
3476
+ return [
3477
+ ...lines,
3478
+ {
3479
+ kind: 'line' as const,
3480
+ label: promoLabel,
3481
+ amount: -effectivePromoDiscountAmount,
3482
+ type: effectivePromoLineType,
3483
+ },
3484
+ ];
3485
+ }, [
3486
+ suppressSelfServeCurrencyUi,
3487
+ selfServePricingConfirmed,
3488
+ checkoutPriceSummaryLines,
3489
+ latestChangeQuote?.serverPreview?.priceSummaryLines,
3490
+ effectivePromoDiscountAmount,
3491
+ effectivePromoLineType,
3492
+ appliedPromoCode,
3493
+ originalReceiptPromoAdjustment.label,
3494
+ t,
3495
+ ]);
3496
+
3497
+ /** Receipt/server lines already include {@link PriceSummary}'s TAX row — do not also pass `taxAmount` or it duplicates. */
3498
+ const priceSummaryLinesIncludeTaxRow = useMemo(
3499
+ () =>
3500
+ checkoutPriceSummaryLinesForCheckout.some(
3501
+ (line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
3502
+ ),
3503
+ [checkoutPriceSummaryLinesForCheckout],
3504
+ );
3505
+ const {
3506
+ editableCheckoutPriceSummaryLines,
3507
+ editableSummaryLineAmountInputs,
3508
+ editableSummaryLineLabelInputs,
3509
+ editableSummaryPreSubtotalDelta,
3510
+ editableSummaryPreSubtotalDebugRows,
3511
+ onLineAmountInputChange: handleEditableLineAmountInputChange,
3512
+ onLineAmountInputBlur: handleEditableLineAmountInputBlur,
3513
+ onLineAmountReset: handleEditableLineAmountReset,
3514
+ } = useEditableSummaryLines(
3515
+ checkoutPriceSummaryLinesForCheckout,
3516
+ {
3517
+ enabled: showProviderPricingInlineEditor,
3518
+ onChange: providerPricingUi?.onLineAmountInputChange,
3519
+ onBlur: providerPricingUi?.onLineAmountInputBlur,
3520
+ onReset: providerPricingUi?.onLineAmountReset,
3521
+ },
3522
+ );
3449
3523
  /**
3450
3524
  * FE cart rollup for line math, breakdowns, and `clientProposedTotal` hint to the API. Self-serve **footer** totals
3451
3525
  * prefer `latestChangeQuote` once quote succeeds — this value is not an alternate source of truth for checkout.
@@ -4036,9 +4110,14 @@ export function AdminChangeBookingFlow({
4036
4110
  let subtotal = roundMoney(displayChangeFlowSubtotal + editableSummaryPreSubtotalDelta);
4037
4111
  const tax = roundMoney(displayChangeFlowTax);
4038
4112
  const expectedTotal = roundMoney(displayChangeFlowProposedTotalWithEditableLines);
4039
- const recomputed = roundMoney(subtotal + tax);
4113
+ const promoAdjustmentTotal = roundMoney(
4114
+ lineItems.reduce((sum, line) => (
4115
+ isPromoDiscountGiftCardOrVoucherLine(line) ? sum + (Number(line.amount) || 0) : sum
4116
+ ), 0)
4117
+ );
4118
+ const recomputed = roundMoney(subtotal + tax + promoAdjustmentTotal);
4040
4119
  if (Math.abs(recomputed - expectedTotal) >= 0.005) {
4041
- subtotal = roundMoney(expectedTotal - tax);
4120
+ subtotal = roundMoney(expectedTotal - tax - promoAdjustmentTotal);
4042
4121
  }
4043
4122
  return {
4044
4123
  subtotal,
@@ -5362,9 +5441,9 @@ export function AdminChangeBookingFlow({
5362
5441
  ...(effectivePromoDiscountAmount > 0
5363
5442
  ? [
5364
5443
  {
5365
- label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
5444
+ label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
5366
5445
  amount: -effectivePromoDiscountAmount,
5367
- type: isGiftCard ? 'GIFT_CARD' : 'PROMO_CODE',
5446
+ type: effectivePromoLineType,
5368
5447
  },
5369
5448
  ]
5370
5449
  : []),
@@ -5440,7 +5519,7 @@ export function AdminChangeBookingFlow({
5440
5519
  isTaxIncludedInPrice,
5441
5520
  taxRate: pricingConfig?.taxRate ?? 0,
5442
5521
  promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
5443
- discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
5522
+ discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || undefined),
5444
5523
  });
5445
5524
  setShowAdminPaymentChoice(true);
5446
5525
  setLoading(false);
@@ -5491,7 +5570,7 @@ export function AdminChangeBookingFlow({
5491
5570
  isTaxIncludedInPrice,
5492
5571
  taxRate: pricingConfig?.taxRate ?? 0,
5493
5572
  promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
5494
- discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
5573
+ discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || undefined),
5495
5574
  changeTotals:
5496
5575
  isCustomerSelfServeChange && originalReceipt
5497
5576
  ? {