@ticketboothapp/booking 1.2.97 → 1.2.98

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.98",
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
 
@@ -3436,16 +3441,88 @@ export function AdminChangeBookingFlow({
3436
3441
  // Change booking: get-promo-discount uses forBookingChange + prior promo from receipt so expired codes stay locked (BE).
3437
3442
  const lockedPromoFallbackAmount =
3438
3443
  lockedPromoCode
3439
- ? Math.max(0, originalReceipt?.promoAmount ?? 0)
3444
+ ? Math.max(0, originalReceiptPromoAdjustment.amount)
3440
3445
  : 0;
3441
3446
  const effectivePromoDiscountAmount =
3442
3447
  promoDiscountAmount > 0 ? promoDiscountAmount : lockedPromoFallbackAmount;
3448
+ const effectivePromoLineType =
3449
+ isGiftCard || originalReceiptPromoAdjustment.type === 'GIFT_CARD'
3450
+ ? 'GIFT_CARD'
3451
+ : isVoucher || originalReceiptPromoAdjustment.type === 'VOUCHER'
3452
+ ? 'VOUCHER'
3453
+ : originalReceiptPromoAdjustment.type;
3443
3454
  const taxOnSubtotal = isTaxIncludedInPrice ? 0 : effectiveSubtotalForCheckout * (pricingConfig?.taxRate ?? 0);
3444
3455
  const effectiveTax =
3445
- effectivePromoDiscountAmount > 0 && !isGiftCard && !isVoucher
3456
+ effectivePromoDiscountAmount > 0 && effectivePromoLineType !== 'GIFT_CARD' && effectivePromoLineType !== 'VOUCHER'
3446
3457
  ? (effectiveSubtotalForCheckout - effectivePromoDiscountAmount) * (pricingConfig?.taxRate ?? 0)
3447
3458
  : taxOnSubtotal;
3448
3459
  const totalPrice = effectiveSubtotalForCheckout + effectiveTax - effectivePromoDiscountAmount;
3460
+
3461
+ const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
3462
+ let raw: PriceSummaryLine[];
3463
+ if (suppressSelfServeCurrencyUi && selfServePricingConfirmed) {
3464
+ const serverLines = latestChangeQuote?.serverPreview?.priceSummaryLines;
3465
+ raw = serverLines && serverLines.length > 0 ? serverLines : checkoutPriceSummaryLines;
3466
+ } else {
3467
+ raw = checkoutPriceSummaryLines;
3468
+ }
3469
+ const lines = omitZeroAmountPromoDiscountSummaryLines(raw);
3470
+ const hasPromoSummaryLine = lines.some((line) =>
3471
+ line.kind === 'line' && isPromoDiscountGiftCardOrVoucherLine(line)
3472
+ );
3473
+ if (hasPromoSummaryLine || effectivePromoDiscountAmount < 0.005) return lines;
3474
+ const trimmedPromoCode = appliedPromoCode?.trim() ?? '';
3475
+ const promoLabel =
3476
+ trimmedPromoCode.length > 0
3477
+ ? `Promo: ${trimmedPromoCode}`
3478
+ : originalReceiptPromoAdjustment.label || (t('booking.discount') || 'Discount');
3479
+ return [
3480
+ ...lines,
3481
+ {
3482
+ kind: 'line' as const,
3483
+ label: promoLabel,
3484
+ amount: -effectivePromoDiscountAmount,
3485
+ type: effectivePromoLineType,
3486
+ },
3487
+ ];
3488
+ }, [
3489
+ suppressSelfServeCurrencyUi,
3490
+ selfServePricingConfirmed,
3491
+ checkoutPriceSummaryLines,
3492
+ latestChangeQuote?.serverPreview?.priceSummaryLines,
3493
+ effectivePromoDiscountAmount,
3494
+ effectivePromoLineType,
3495
+ appliedPromoCode,
3496
+ originalReceiptPromoAdjustment.label,
3497
+ t,
3498
+ ]);
3499
+
3500
+ /** Receipt/server lines already include {@link PriceSummary}'s TAX row — do not also pass `taxAmount` or it duplicates. */
3501
+ const priceSummaryLinesIncludeTaxRow = useMemo(
3502
+ () =>
3503
+ checkoutPriceSummaryLinesForCheckout.some(
3504
+ (line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
3505
+ ),
3506
+ [checkoutPriceSummaryLinesForCheckout],
3507
+ );
3508
+ const {
3509
+ editableCheckoutPriceSummaryLines,
3510
+ editableSummaryLineAmountInputs,
3511
+ editableSummaryLineLabelInputs,
3512
+ editableSummaryPreSubtotalDelta,
3513
+ editableSummaryPreSubtotalDebugRows,
3514
+ onLineAmountInputChange: handleEditableLineAmountInputChange,
3515
+ onLineAmountInputBlur: handleEditableLineAmountInputBlur,
3516
+ onLineAmountReset: handleEditableLineAmountReset,
3517
+ } = useEditableSummaryLines(
3518
+ checkoutPriceSummaryLinesForCheckout,
3519
+ {
3520
+ enabled: showProviderPricingInlineEditor,
3521
+ onChange: providerPricingUi?.onLineAmountInputChange,
3522
+ onBlur: providerPricingUi?.onLineAmountInputBlur,
3523
+ onReset: providerPricingUi?.onLineAmountReset,
3524
+ },
3525
+ );
3449
3526
  /**
3450
3527
  * FE cart rollup for line math, breakdowns, and `clientProposedTotal` hint to the API. Self-serve **footer** totals
3451
3528
  * prefer `latestChangeQuote` once quote succeeds — this value is not an alternate source of truth for checkout.
@@ -4036,9 +4113,14 @@ export function AdminChangeBookingFlow({
4036
4113
  let subtotal = roundMoney(displayChangeFlowSubtotal + editableSummaryPreSubtotalDelta);
4037
4114
  const tax = roundMoney(displayChangeFlowTax);
4038
4115
  const expectedTotal = roundMoney(displayChangeFlowProposedTotalWithEditableLines);
4039
- const recomputed = roundMoney(subtotal + tax);
4116
+ const promoAdjustmentTotal = roundMoney(
4117
+ lineItems.reduce((sum, line) => (
4118
+ isPromoDiscountGiftCardOrVoucherLine(line) ? sum + (Number(line.amount) || 0) : sum
4119
+ ), 0)
4120
+ );
4121
+ const recomputed = roundMoney(subtotal + tax + promoAdjustmentTotal);
4040
4122
  if (Math.abs(recomputed - expectedTotal) >= 0.005) {
4041
- subtotal = roundMoney(expectedTotal - tax);
4123
+ subtotal = roundMoney(expectedTotal - tax - promoAdjustmentTotal);
4042
4124
  }
4043
4125
  return {
4044
4126
  subtotal,
@@ -5364,7 +5446,7 @@ export function AdminChangeBookingFlow({
5364
5446
  {
5365
5447
  label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
5366
5448
  amount: -effectivePromoDiscountAmount,
5367
- type: isGiftCard ? 'GIFT_CARD' : 'PROMO_CODE',
5449
+ type: effectivePromoLineType,
5368
5450
  },
5369
5451
  ]
5370
5452
  : []),