@ticketboothapp/booking 1.2.121 → 1.2.122

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.121",
3
+ "version": "1.2.122",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -119,6 +119,27 @@ interface PrivateShuttleBookingFlowProps {
119
119
  const RESOURCE_CAPACITY = 13;
120
120
  const ADDITIONAL_HOUR_PRICE = 170;
121
121
  const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
122
+ const MORAINE_LAKE_ROAD_ACCESS_FEE_TOOLTIP =
123
+ "Since 2025, Parks Canada charges a per-trip fee for License of Occupation at Moraine Lake. This fee contributes towards Parks Canada's Moraine Lake Road operations.";
124
+
125
+ function isMoraineLakeRoadAccessFeeName(name: string): boolean {
126
+ const normalized = name.toLowerCase();
127
+ return (
128
+ normalized.includes('moraine') &&
129
+ (normalized.includes('access') ||
130
+ normalized.includes('road') ||
131
+ normalized.includes('license'))
132
+ );
133
+ }
134
+
135
+ function isMoraineLakeLocationText(value: string): boolean {
136
+ const normalized = value.toLowerCase().replace(/[_-]+/g, ' ');
137
+ return normalized.includes('moraine') && normalized.includes('lake');
138
+ }
139
+
140
+ function roundMoney(amount: number): number {
141
+ return Math.round(amount * 100) / 100;
142
+ }
122
143
 
123
144
  function parseAvailabilityDateTime(value: string): Date {
124
145
  // If API omits timezone offset, treat it as UTC to prevent user-local day shifts.
@@ -1095,17 +1116,38 @@ export function PrivateShuttleBookingFlow({
1095
1116
  const cancellationPolicyFee = selectedCancellationPolicy
1096
1117
  ? (selectedCancellationPolicy.feeByCurrency[currency] ?? 0)
1097
1118
  : 0;
1119
+ const draftItineraryIncludesMoraineLake = useMemo(() => {
1120
+ if (draftItineraryDestinations.length === 0) return false;
1121
+ const destinationLabelsById = new Map(
1122
+ (product.itineraryBuilder?.destinations ?? []).map((destination) => [
1123
+ destination.id,
1124
+ destination.label,
1125
+ ])
1126
+ );
1127
+ return draftItineraryDestinations.some((destinationId) => {
1128
+ const label = destinationLabelsById.get(destinationId);
1129
+ return (
1130
+ isMoraineLakeLocationText(destinationId) ||
1131
+ (label ? isMoraineLakeLocationText(label) : false)
1132
+ );
1133
+ });
1134
+ }, [draftItineraryDestinations, product.itineraryBuilder?.destinations]);
1098
1135
  const perBookingFeeLineItems = useMemo(() => {
1099
1136
  if (isTaxIncludedInPrice) return [];
1100
1137
  const amountsByName = pricingConfig?.perBookingFeesByCurrency?.[currency];
1101
1138
  if (!amountsByName) return [];
1102
1139
  const feeMetadata = pricingConfig?.perBookingFees ?? {};
1103
- return Object.entries(amountsByName).map(([name, amount]) => ({
1104
- name,
1105
- totalAmount: amount,
1106
- description: feeMetadata[name]?.description,
1107
- }));
1108
- }, [currency, isTaxIncludedInPrice, pricingConfig]);
1140
+ return Object.entries(amountsByName)
1141
+ .filter(
1142
+ ([name]) =>
1143
+ draftItineraryIncludesMoraineLake || !isMoraineLakeRoadAccessFeeName(name)
1144
+ )
1145
+ .map(([name, amount]) => ({
1146
+ name,
1147
+ totalAmount: amount,
1148
+ description: feeMetadata[name]?.description,
1149
+ }));
1150
+ }, [currency, draftItineraryIncludesMoraineLake, isTaxIncludedInPrice, pricingConfig]);
1109
1151
 
1110
1152
  const addOnTotal = useMemo(() => {
1111
1153
  let sum = 0;
@@ -1134,6 +1176,116 @@ export function PrivateShuttleBookingFlow({
1134
1176
  ? (isTaxIncludedInPrice ? 0 : (subtotal - effectivePromoDiscountAmount) * taxRate)
1135
1177
  : taxAmount;
1136
1178
  const totalPrice = subtotal + effectiveTaxAmount - effectivePromoDiscountAmount;
1179
+ const providerChangeAuthoritativeReceipt = useMemo<
1180
+ ProviderDashboardChangeBookingPayload['authoritativeReceipt']
1181
+ >(() => {
1182
+ const lineItems: NonNullable<
1183
+ ProviderDashboardChangeBookingPayload['authoritativeReceipt']
1184
+ >['lineItems'] = [
1185
+ {
1186
+ type: 'TICKET',
1187
+ label: resourceCount > 1 ? 'Shuttles' : 'Shuttle',
1188
+ amount: roundMoney(basePrice),
1189
+ quantity: resourceCount,
1190
+ },
1191
+ ];
1192
+
1193
+ for (const sel of addOnSelections) {
1194
+ const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
1195
+ if (!addOn) continue;
1196
+ const hasVariant =
1197
+ (addOn.variantType === 'single_choice' || addOn.variantType === 'multi_quantity') &&
1198
+ sel.variantId;
1199
+ const variant = hasVariant
1200
+ ? addOn.variants?.find((v) => v.id === sel.variantId)
1201
+ : null;
1202
+ const amount = ((addOn.price ?? 0) + (variant?.priceAdjustment ?? 0)) * (sel.quantity ?? 1);
1203
+ lineItems.push({
1204
+ type: 'FEE',
1205
+ label: variant?.label ? `${addOn.name} (${variant.label})` : addOn.name,
1206
+ amount: roundMoney(amount),
1207
+ });
1208
+ }
1209
+
1210
+ if (additionalHoursAmount > 0) {
1211
+ lineItems.push({
1212
+ type: 'FEE',
1213
+ label:
1214
+ additionalHoursCount === 1
1215
+ ? 'Additional hour'
1216
+ : `Additional hours (${additionalHoursCount})`,
1217
+ amount: roundMoney(additionalHoursAmount),
1218
+ });
1219
+ }
1220
+
1221
+ for (const fee of perBookingFeeLineItems) {
1222
+ lineItems.push({
1223
+ type: 'FEE',
1224
+ label: fee.name,
1225
+ amount: roundMoney(fee.totalAmount),
1226
+ });
1227
+ }
1228
+
1229
+ if (cancellationPolicyFee > 0 && selectedCancellationPolicy) {
1230
+ lineItems.push({
1231
+ type: 'CANCELLATION_UPGRADE',
1232
+ label: selectedCancellationPolicy.label,
1233
+ amount: roundMoney(cancellationPolicyFee),
1234
+ });
1235
+ }
1236
+
1237
+ if (effectiveTaxAmount > 0) {
1238
+ lineItems.push({
1239
+ type: 'TAX',
1240
+ label: t('booking.tax') !== 'booking.tax' ? t('booking.tax') : 'Taxes and fees',
1241
+ amount: roundMoney(effectiveTaxAmount),
1242
+ });
1243
+ }
1244
+
1245
+ if (effectivePromoDiscountAmount > 0) {
1246
+ lineItems.push({
1247
+ type: isGiftCard ? 'GIFT_CARD' : 'PROMO_CODE',
1248
+ label: activePromoCode ? `Promo: ${activePromoCode}` : (t('booking.discount') || 'Discount'),
1249
+ amount: roundMoney(-effectivePromoDiscountAmount),
1250
+ });
1251
+ }
1252
+
1253
+ const expectedTotal = roundMoney(totalPrice);
1254
+ const lineTotal = roundMoney(lineItems.reduce((sum, line) => sum + line.amount, 0));
1255
+ const roundingDelta = roundMoney(expectedTotal - lineTotal);
1256
+ if (Math.abs(roundingDelta) >= 0.01) {
1257
+ lineItems.push({
1258
+ type: 'ROUNDING',
1259
+ label: t('booking.rounding') || 'Rounding',
1260
+ amount: roundingDelta,
1261
+ });
1262
+ }
1263
+
1264
+ return {
1265
+ currency,
1266
+ lineItems,
1267
+ subtotalBeforeTax: roundMoney(expectedTotal - roundMoney(effectiveTaxAmount)),
1268
+ taxAmount: roundMoney(effectiveTaxAmount),
1269
+ totalAmount: expectedTotal,
1270
+ };
1271
+ }, [
1272
+ activePromoCode,
1273
+ addOnSelections,
1274
+ addOns,
1275
+ additionalHoursAmount,
1276
+ additionalHoursCount,
1277
+ basePrice,
1278
+ cancellationPolicyFee,
1279
+ currency,
1280
+ effectivePromoDiscountAmount,
1281
+ effectiveTaxAmount,
1282
+ isGiftCard,
1283
+ perBookingFeeLineItems,
1284
+ resourceCount,
1285
+ selectedCancellationPolicy,
1286
+ t,
1287
+ totalPrice,
1288
+ ]);
1137
1289
 
1138
1290
  useEffect(() => {
1139
1291
  if (!onPricePreviewChange) return;
@@ -1657,6 +1809,7 @@ export function PrivateShuttleBookingFlow({
1657
1809
  promoCode: activePromoCode ?? null,
1658
1810
  newTotalAmount: totalPrice,
1659
1811
  additionalHoursCount: isAdmin ? additionalHoursCount : null,
1812
+ authoritativeReceipt: providerChangeAuthoritativeReceipt,
1660
1813
  });
1661
1814
  setLoading(false);
1662
1815
  return;
@@ -2939,6 +3092,9 @@ export function PrivateShuttleBookingFlow({
2939
3092
  label: fee.name,
2940
3093
  amount: fee.totalAmount,
2941
3094
  type: 'fee' as const,
3095
+ tooltip: isMoraineLakeRoadAccessFeeName(fee.name)
3096
+ ? MORAINE_LAKE_ROAD_ACCESS_FEE_TOOLTIP
3097
+ : undefined,
2942
3098
  })),
2943
3099
  ...(cancellationPolicyFee > 0
2944
3100
  ? [