@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
|
@@ -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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 :
|
|
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 &&
|
|
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
|
|
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:
|
|
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
|
? {
|