@bash-app/bash-common 30.3.0 → 30.5.0
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 +1 -1
- package/prisma/schema.prisma +140 -87
- package/scripts/symlinks.sh +2 -0
- package/src/definitions.ts +37 -43
- package/src/extendedSchemas.ts +122 -104
- package/src/index.ts +4 -3
- package/src/utils/arrayUtils.ts +8 -0
- package/src/utils/luxonUtils.ts +78 -15
- package/src/utils/service/apiServiceBookingApiUtils.ts +222 -0
- package/src/utils/service/frontendServiceBookingUtils.ts +310 -0
- package/src/utils/service/serviceBookingStatusUtils.ts +96 -27
- package/src/utils/service/serviceBookingTypes.ts +56 -0
- package/src/utils/service/serviceDBUtils.ts +35 -35
- package/src/utils/service/serviceRateDBUtils.ts +179 -179
- package/src/utils/service/serviceRateTypes.ts +18 -0
- package/src/utils/service/serviceRateUtils.ts +38 -64
- package/src/utils/service/serviceUtils.ts +35 -3
- package/src/utils/stringUtils.ts +1 -1
- package/src/utils/service/serviceBookingApiUtils.ts +0 -259
- package/src/utils/service/serviceBookingUtils.ts +0 -391
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApiBookingParamsLuxon,
|
|
3
|
+
ApiServiceBookingParams,
|
|
4
|
+
ApiServiceBookedDayParamsLuxon,
|
|
5
|
+
ApiServiceAddonParams,
|
|
6
|
+
ApiServiceBookedDayParams,
|
|
7
|
+
ApiServicePriceToBookResult,
|
|
8
|
+
} from "../../definitions";
|
|
9
|
+
import {
|
|
10
|
+
ServiceAddonExt,
|
|
11
|
+
ServiceBookingAddOnExt,
|
|
12
|
+
ServiceBookingDayExt,
|
|
13
|
+
ServiceBookingExt,
|
|
14
|
+
ServiceBookingFeeExt,
|
|
15
|
+
ServiceBookingPriceBreakdownExt,
|
|
16
|
+
ServiceExt,
|
|
17
|
+
} from "../../extendedSchemas";
|
|
18
|
+
import { dateTimeFromString, dateTimeRangeFromDates } from "../luxonUtils";
|
|
19
|
+
import { serviceAttendeeOptions } from "./attendeeOptionUtils";
|
|
20
|
+
import { ServiceBookingPriceBreakdownBaseT } from "./serviceBookingTypes";
|
|
21
|
+
import {
|
|
22
|
+
ServiceAddonInput,
|
|
23
|
+
FrontendServiceGetPriceToBookResult,
|
|
24
|
+
FrontendServiceBookingDayInfo,
|
|
25
|
+
} from "./frontendServiceBookingUtils";
|
|
26
|
+
|
|
27
|
+
export type ApiServiceCantBookReason = {
|
|
28
|
+
type: string;
|
|
29
|
+
msg: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const ApiServiceCantBookReasons = {
|
|
33
|
+
notInBusinessHours: {
|
|
34
|
+
msg: "Time not within normal Business Hours",
|
|
35
|
+
} as ApiServiceCantBookReason,
|
|
36
|
+
minimumTimeBlock: {
|
|
37
|
+
msg: "Minimum time block not satisfied",
|
|
38
|
+
} as ApiServiceCantBookReason,
|
|
39
|
+
dateBlocked: {
|
|
40
|
+
msg: "The selected date is blocked.",
|
|
41
|
+
} as ApiServiceCantBookReason,
|
|
42
|
+
alreadyBooked: {
|
|
43
|
+
msg: "This service has already been booked.",
|
|
44
|
+
} as ApiServiceCantBookReason,
|
|
45
|
+
requestDeclined: {
|
|
46
|
+
msg: "The booking request has been declined.",
|
|
47
|
+
} as ApiServiceCantBookReason,
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export type ApiServiceCantBookReasons = keyof typeof ApiServiceCantBookReasons;
|
|
51
|
+
|
|
52
|
+
export function apiTransformAddOnsToAddonInputs<
|
|
53
|
+
AddonParams_t extends ApiServiceAddonParams
|
|
54
|
+
>(
|
|
55
|
+
addOnsApi: AddonParams_t[],
|
|
56
|
+
serviceAddons: ServiceAddonExt[]
|
|
57
|
+
): ServiceAddonInput[] {
|
|
58
|
+
// Create a Map for faster lookup by addonId
|
|
59
|
+
const addonMap = new Map<string, ServiceAddonExt>(
|
|
60
|
+
serviceAddons.map((addon) => [addon.id, addon])
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return addOnsApi.map((addOnApi) => {
|
|
64
|
+
const matchedAddon = addonMap.get(addOnApi.addonId);
|
|
65
|
+
if (!matchedAddon) {
|
|
66
|
+
throw new Error(`Failed to match addon with id: ${addOnApi.addonId}`);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...matchedAddon,
|
|
70
|
+
...addOnApi,
|
|
71
|
+
} as ServiceAddonInput;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function apiTransformBookedDays(
|
|
76
|
+
priceToBook: ApiServicePriceToBookResult
|
|
77
|
+
): Partial<ServiceBookingDayExt>[] {
|
|
78
|
+
return priceToBook.daysToBook.map((day): Partial<ServiceBookingDayExt> => {
|
|
79
|
+
// Transform addOns using the helper function, then map to the expected output.
|
|
80
|
+
const addOns = day.addOns.map(
|
|
81
|
+
(addOn): Partial<ServiceBookingAddOnExt> => ({
|
|
82
|
+
addOnId: addOn.addOnId,
|
|
83
|
+
addOn: addOn.addOn,
|
|
84
|
+
chosenQuantity: addOn.chosenQuantity,
|
|
85
|
+
costPQCents: addOn.costPQCents,
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const priceBreakdown = day.priceBreakdown.map(
|
|
90
|
+
(pb): Partial<ServiceBookingPriceBreakdownExt> => {
|
|
91
|
+
const { dateTimeRange, ...pbData } = pb;
|
|
92
|
+
return {
|
|
93
|
+
...pbData,
|
|
94
|
+
startDate: pb.dateTimeRange.start.toJSDate(),
|
|
95
|
+
endDate: pb.dateTimeRange.end.toJSDate(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
startDate: day.dateTimeRange.start.toJSDate(),
|
|
102
|
+
endDate: day.dateTimeRange.end.toJSDate(),
|
|
103
|
+
addOns: addOns,
|
|
104
|
+
packages: [],
|
|
105
|
+
fees: day.fees as ServiceBookingFeeExt[],
|
|
106
|
+
priceBreakdown: priceBreakdown,
|
|
107
|
+
totalBeforeTaxesCents: day.totalBeforeTaxesCents,
|
|
108
|
+
} as Partial<ServiceBookingDayExt>;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function serviceGetPriceToBookFromBooking(
|
|
113
|
+
service: ServiceExt,
|
|
114
|
+
booking: ServiceBookingExt
|
|
115
|
+
): FrontendServiceGetPriceToBookResult {
|
|
116
|
+
const daysToBook = booking.bookedDays
|
|
117
|
+
.map((day) => {
|
|
118
|
+
const priceBreakdown = day.priceBreakdown.map(
|
|
119
|
+
(pb): ServiceBookingPriceBreakdownBaseT => ({
|
|
120
|
+
...pb,
|
|
121
|
+
dateTimeRange: dateTimeRangeFromDates(
|
|
122
|
+
pb.startDate,
|
|
123
|
+
pb.endDate,
|
|
124
|
+
"UTC",
|
|
125
|
+
booking.timezone
|
|
126
|
+
),
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
dateTimeRange: dateTimeRangeFromDates(
|
|
132
|
+
day.startDate,
|
|
133
|
+
day.endDate,
|
|
134
|
+
"UTC",
|
|
135
|
+
booking.timezone
|
|
136
|
+
),
|
|
137
|
+
addOns: day.addOns,
|
|
138
|
+
priceBreakdown: priceBreakdown,
|
|
139
|
+
fees: day.fees,
|
|
140
|
+
totalBeforeTaxesCents: day.totalBeforeTaxesCents,
|
|
141
|
+
timezone: booking.timezone,
|
|
142
|
+
} as FrontendServiceBookingDayInfo;
|
|
143
|
+
})
|
|
144
|
+
.sort(
|
|
145
|
+
(lhs, rhs) =>
|
|
146
|
+
lhs.dateTimeRange.start.toMillis() - rhs.dateTimeRange.start.toMillis()
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
serviceId: service.id,
|
|
151
|
+
daysToBook: daysToBook,
|
|
152
|
+
selectedAttendeeOption: serviceAttendeeOptions[0],
|
|
153
|
+
additionalFees: booking.additionalFees,
|
|
154
|
+
timezone: booking.timezone,
|
|
155
|
+
daysTotalBeforeTaxesCents: booking.daysTotalATBCents,
|
|
156
|
+
totalBeforeTaxesCents: booking.totalATBCents,
|
|
157
|
+
} as FrontendServiceGetPriceToBookResult;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function serviceBookingParamsToLuxon(
|
|
161
|
+
params: ApiServiceBookingParams
|
|
162
|
+
): ApiBookingParamsLuxon {
|
|
163
|
+
const bookedDays = params.bookedDays.reduce(
|
|
164
|
+
(sofar, day): ApiServiceBookedDayParamsLuxon[] => {
|
|
165
|
+
sofar.push({
|
|
166
|
+
...day,
|
|
167
|
+
dateTimeRange: {
|
|
168
|
+
start: dateTimeFromString(day.startDate),
|
|
169
|
+
end: dateTimeFromString(day.endDate),
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return sofar;
|
|
174
|
+
},
|
|
175
|
+
[] as ApiServiceBookedDayParamsLuxon[]
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...params,
|
|
180
|
+
bookedDays,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function servicePriceToBookToApiDays(
|
|
185
|
+
serviceId: string,
|
|
186
|
+
userId: string,
|
|
187
|
+
priceToBook: FrontendServiceGetPriceToBookResult
|
|
188
|
+
): ApiServiceBookedDayParams[] {
|
|
189
|
+
const bookedDays: ApiServiceBookedDayParams[] =
|
|
190
|
+
priceToBook.daysToBook
|
|
191
|
+
.map((day): ApiServiceBookedDayParams | null => {
|
|
192
|
+
const addOns: ApiServiceAddonParams[] = day.addOns.map(
|
|
193
|
+
(addOn): ApiServiceAddonParams => ({
|
|
194
|
+
addonId: addOn.addOnId,
|
|
195
|
+
chosenQuantity: addOn.chosenQuantity,
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
const startDate = day.dateTimeRange.start.toISO();
|
|
199
|
+
const endDate = day.dateTimeRange.end.toISO();
|
|
200
|
+
|
|
201
|
+
if (!startDate || !endDate) {
|
|
202
|
+
console.log(`Invalid startDate/endDate: ${startDate}/${endDate}`);
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
serviceId: serviceId,
|
|
208
|
+
forUserId: userId,
|
|
209
|
+
startDate,
|
|
210
|
+
endDate,
|
|
211
|
+
addOns,
|
|
212
|
+
allowPromiseToPay: false,
|
|
213
|
+
isFreeGuest: false,
|
|
214
|
+
};
|
|
215
|
+
})
|
|
216
|
+
.filter(
|
|
217
|
+
(dayParams): dayParams is ApiServiceBookedDayParams =>
|
|
218
|
+
dayParams !== null
|
|
219
|
+
) ?? [];
|
|
220
|
+
|
|
221
|
+
return bookedDays;
|
|
222
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServiceAddon,
|
|
3
|
+
ServiceBookingFee,
|
|
4
|
+
ServiceBookingFeeType,
|
|
5
|
+
} from "@prisma/client";
|
|
6
|
+
import {
|
|
7
|
+
ServiceBookingAddOnExt,
|
|
8
|
+
ServiceBookingDayExt,
|
|
9
|
+
ServiceBookingFeeExt,
|
|
10
|
+
ServiceBookingPriceBreakdownExt,
|
|
11
|
+
ServiceDailyRatesExt,
|
|
12
|
+
ServiceRatesAssociationExt,
|
|
13
|
+
ServiceSpecialRatesExt,
|
|
14
|
+
} from "../../extendedSchemas";
|
|
15
|
+
import { ServiceAttendeeOption } from "./attendeeOptionUtils";
|
|
16
|
+
import {
|
|
17
|
+
getServiceRatePricingInfo,
|
|
18
|
+
SERVICE_DAILY_RATE_HOURS_MIN,
|
|
19
|
+
serviceGetFilteredRates,
|
|
20
|
+
serviceRatesFilter,
|
|
21
|
+
serviceRatesHasRates,
|
|
22
|
+
ServiceRatesLuxon,
|
|
23
|
+
} from "./serviceRateUtils";
|
|
24
|
+
import {
|
|
25
|
+
dateTimeDiffHours,
|
|
26
|
+
dateTimeRangeHours,
|
|
27
|
+
LuxonDateRange,
|
|
28
|
+
} from "../luxonUtils";
|
|
29
|
+
import {
|
|
30
|
+
ServiceBookingFeeBase,
|
|
31
|
+
ServiceBookingPriceBreakdownBaseT,
|
|
32
|
+
ServiceBookingAddOnBase,
|
|
33
|
+
} from "./serviceBookingTypes";
|
|
34
|
+
import { convertDollarsToCents } from "../paymentUtils";
|
|
35
|
+
|
|
36
|
+
export const SERVICE_BOOKING_PROCESSING_FEE_PERCENT = 0.15;
|
|
37
|
+
|
|
38
|
+
export interface ServiceAddonInput extends ServiceAddon {
|
|
39
|
+
chosenQuantity?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FrontendServiceBookingDayInfoParams {
|
|
43
|
+
dateTimeRange: LuxonDateRange;
|
|
44
|
+
fees: ServiceBookingFeeBase[];
|
|
45
|
+
addOns: ServiceAddonInput[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface FrontendServiceBookingDayInfo {
|
|
49
|
+
priceBreakdown: ServiceBookingPriceBreakdownBaseT[];
|
|
50
|
+
addOns: ServiceBookingAddOnBase[];
|
|
51
|
+
fees: ServiceBookingFeeBase[];
|
|
52
|
+
// discounts: ServiceRatePricingInfo[];
|
|
53
|
+
|
|
54
|
+
dateTimeRange: LuxonDateRange;
|
|
55
|
+
timezone: string;
|
|
56
|
+
|
|
57
|
+
totalBeforeTaxesCents: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface FrontendServiceGetPriceToBookResult {
|
|
61
|
+
serviceId: string;
|
|
62
|
+
|
|
63
|
+
daysToBook: FrontendServiceBookingDayInfo[];
|
|
64
|
+
additionalFees: ServiceBookingFeeBase[];
|
|
65
|
+
|
|
66
|
+
daysTotalBeforeTaxesCents: number;
|
|
67
|
+
totalBeforeTaxesCents: number;
|
|
68
|
+
|
|
69
|
+
minimumTimeBlockHours?: number; //only valid on generalRate
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FrontendServiceGetBookingDayInfoFrontParams {
|
|
73
|
+
filteredRates: ServiceRatesLuxon;
|
|
74
|
+
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
75
|
+
|
|
76
|
+
timezone: string | null | undefined;
|
|
77
|
+
daysToBookParams: FrontendServiceBookingDayInfoParams[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function frontendServiceGetBookingDayInfo({
|
|
81
|
+
filteredRates,
|
|
82
|
+
daysToBookParams,
|
|
83
|
+
// overlappingBusinessHours,
|
|
84
|
+
timezone,
|
|
85
|
+
}: FrontendServiceGetBookingDayInfoFrontParams): FrontendServiceBookingDayInfo[] {
|
|
86
|
+
const bookingDayResults: FrontendServiceBookingDayInfo[] =
|
|
87
|
+
daysToBookParams.map((day) => {
|
|
88
|
+
const rates = serviceRatesFilter(filteredRates, [day.dateTimeRange]);
|
|
89
|
+
const { generalRate, specialRates, dailyRates } = rates;
|
|
90
|
+
|
|
91
|
+
let priceBreakdown: ServiceBookingPriceBreakdownBaseT[] = [];
|
|
92
|
+
const durationHours = dateTimeDiffHours(
|
|
93
|
+
day.dateTimeRange.start,
|
|
94
|
+
day.dateTimeRange.end
|
|
95
|
+
);
|
|
96
|
+
// console.log(
|
|
97
|
+
// `serviceGetBookingDayInfo: durationHours: ${JSON.stringify(
|
|
98
|
+
// durationHours
|
|
99
|
+
// )}, dtRange: ${JSON.stringify(day.dateTimeRange)}`
|
|
100
|
+
// );
|
|
101
|
+
let hoursRemaining = durationHours;
|
|
102
|
+
let currentRange = { ...day.dateTimeRange };
|
|
103
|
+
let baseCostDiscountedCents = 0;
|
|
104
|
+
while (serviceRatesHasRates(rates) && hoursRemaining > 0) {
|
|
105
|
+
const pricingInfo = getServiceRatePricingInfo(
|
|
106
|
+
currentRange,
|
|
107
|
+
generalRate,
|
|
108
|
+
dailyRates,
|
|
109
|
+
specialRates,
|
|
110
|
+
// overlappingBusinessHours,
|
|
111
|
+
timezone,
|
|
112
|
+
hoursRemaining
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (pricingInfo.hours == 0 || pricingInfo.unitCount == 0) {
|
|
116
|
+
throw new Error(`Invalid pricingInfo`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
priceBreakdown.push(pricingInfo);
|
|
120
|
+
|
|
121
|
+
// currentRange.start = currentRange.start.plus({
|
|
122
|
+
// hours: pricingInfo.hours,
|
|
123
|
+
// });
|
|
124
|
+
currentRange.start = pricingInfo.dateTimeRange.end;
|
|
125
|
+
hoursRemaining -= pricingInfo.hours;
|
|
126
|
+
baseCostDiscountedCents += pricingInfo.piTotalCents;
|
|
127
|
+
|
|
128
|
+
if (pricingInfo.hours < SERVICE_DAILY_RATE_HOURS_MIN) {
|
|
129
|
+
if (pricingInfo.rateType == "Special") {
|
|
130
|
+
specialRates.pop();
|
|
131
|
+
} else if (pricingInfo.rateType == "Weekday") {
|
|
132
|
+
dailyRates.pop();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const addOns = day.addOns.map((addOn): ServiceBookingAddOnBase => {
|
|
138
|
+
return {
|
|
139
|
+
addOn: addOn,
|
|
140
|
+
addOnId: addOn.id,
|
|
141
|
+
chosenQuantity: addOn.chosenQuantity ?? 0,
|
|
142
|
+
costPQCents: addOn.priceCents,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const feesTotalCents = day.fees.reduce(
|
|
147
|
+
(sofar, fee) => sofar + fee.costPQCents * fee.quantity,
|
|
148
|
+
0
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const addOnsTotalCents = addOns.reduce(
|
|
152
|
+
(sofar, addOn) => sofar + addOn.costPQCents * addOn.chosenQuantity,
|
|
153
|
+
0
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const totalBeforeTaxesCents =
|
|
157
|
+
baseCostDiscountedCents + addOnsTotalCents + feesTotalCents;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
priceBreakdown: priceBreakdown,
|
|
161
|
+
dateTimeRange: day.dateTimeRange,
|
|
162
|
+
timezone: day.dateTimeRange.start.zoneName,
|
|
163
|
+
fees: day.fees,
|
|
164
|
+
addOns: addOns,
|
|
165
|
+
totalBeforeTaxesCents: totalBeforeTaxesCents,
|
|
166
|
+
} as FrontendServiceBookingDayInfo;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return bookingDayResults;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface FrontendServiceGetPriceToBookFeesParams {
|
|
173
|
+
daysToBookParams: FrontendServiceBookingDayInfoParams[];
|
|
174
|
+
selectedAttendeeOption: ServiceAttendeeOption;
|
|
175
|
+
|
|
176
|
+
additionalFees: ServiceBookingFeeBase[];
|
|
177
|
+
|
|
178
|
+
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
179
|
+
timezone: string | null | undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function frontendServiceGetPriceToBookFees(
|
|
183
|
+
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
184
|
+
{
|
|
185
|
+
daysToBookParams,
|
|
186
|
+
selectedAttendeeOption,
|
|
187
|
+
additionalFees,
|
|
188
|
+
// overlappingBusinessHours,
|
|
189
|
+
timezone,
|
|
190
|
+
}: FrontendServiceGetPriceToBookFeesParams
|
|
191
|
+
): FrontendServiceGetPriceToBookResult {
|
|
192
|
+
const bookingDateTimeRanges = daysToBookParams.map(
|
|
193
|
+
(daysToBookParams) => daysToBookParams.dateTimeRange
|
|
194
|
+
);
|
|
195
|
+
const filteredRates = serviceGetFilteredRates(
|
|
196
|
+
serviceRatesAssociation,
|
|
197
|
+
bookingDateTimeRanges,
|
|
198
|
+
timezone
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const bookingDayResults: FrontendServiceBookingDayInfo[] =
|
|
202
|
+
frontendServiceGetBookingDayInfo({
|
|
203
|
+
filteredRates: filteredRates,
|
|
204
|
+
daysToBookParams: daysToBookParams,
|
|
205
|
+
// overlappingBusinessHours: overlappingBusinessHours,
|
|
206
|
+
timezone: timezone,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const daysTotalBeforeTaxesCents = bookingDayResults.reduce((sofar, curr) => {
|
|
210
|
+
return sofar + curr.totalBeforeTaxesCents;
|
|
211
|
+
}, 0);
|
|
212
|
+
|
|
213
|
+
const minimumTimeBlockHours =
|
|
214
|
+
serviceRatesAssociation?.serviceGeneralRates?.minimumTimeBlockHours;
|
|
215
|
+
|
|
216
|
+
// const attendeeRate = selectedAttendeeOption.rate * durationHours;
|
|
217
|
+
|
|
218
|
+
const processingFeeCents =
|
|
219
|
+
SERVICE_BOOKING_PROCESSING_FEE_PERCENT * daysTotalBeforeTaxesCents;
|
|
220
|
+
|
|
221
|
+
const hasCustomProcessingFee = additionalFees.some(
|
|
222
|
+
(fee) => fee.feeType == ServiceBookingFeeType.ProcessingFee
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
let allAdditionalFees = [...additionalFees];
|
|
226
|
+
|
|
227
|
+
if (!hasCustomProcessingFee) {
|
|
228
|
+
allAdditionalFees.push({
|
|
229
|
+
feeType: ServiceBookingFeeType.ProcessingFee,
|
|
230
|
+
name: "Processing Fee",
|
|
231
|
+
description: "Processing Fee",
|
|
232
|
+
costPQCents: processingFeeCents,
|
|
233
|
+
quantity: 1,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
allAdditionalFees = allAdditionalFees.filter((fee) => fee.costPQCents > 0);
|
|
238
|
+
|
|
239
|
+
const allAdditionalFeesCents = allAdditionalFees.reduce(
|
|
240
|
+
(sofar, fee) => sofar + fee.costPQCents * fee.quantity,
|
|
241
|
+
0
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const totalBeforeTaxesCents =
|
|
245
|
+
daysTotalBeforeTaxesCents + allAdditionalFeesCents;
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
serviceId: serviceRatesAssociation?.serviceId,
|
|
249
|
+
daysToBook: bookingDayResults,
|
|
250
|
+
daysTotalBeforeTaxesCents: daysTotalBeforeTaxesCents,
|
|
251
|
+
totalBeforeTaxesCents: totalBeforeTaxesCents,
|
|
252
|
+
additionalFees: allAdditionalFees,
|
|
253
|
+
minimumTimeBlockHours: minimumTimeBlockHours,
|
|
254
|
+
} as FrontendServiceGetPriceToBookResult;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface FrontendServiceGetPriceToBookParams {
|
|
258
|
+
addOns: ServiceAddonInput[];
|
|
259
|
+
|
|
260
|
+
selectedAttendeeOption: ServiceAttendeeOption;
|
|
261
|
+
|
|
262
|
+
bookingDateTimeRanges: LuxonDateRange[];
|
|
263
|
+
|
|
264
|
+
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
265
|
+
timezone: string | null | undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function frontendServiceGetPriceToBook(
|
|
269
|
+
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
270
|
+
{
|
|
271
|
+
addOns,
|
|
272
|
+
selectedAttendeeOption,
|
|
273
|
+
bookingDateTimeRanges, //list of dateTime ranges to book
|
|
274
|
+
// overlappingBusinessHours,
|
|
275
|
+
timezone,
|
|
276
|
+
}: FrontendServiceGetPriceToBookParams
|
|
277
|
+
): FrontendServiceGetPriceToBookResult {
|
|
278
|
+
const cleaningFeePerBookingCents =
|
|
279
|
+
serviceRatesAssociation?.serviceGeneralRates?.cleaningFeePerBookingCents ??
|
|
280
|
+
0;
|
|
281
|
+
|
|
282
|
+
const feesPerBooking = (
|
|
283
|
+
[
|
|
284
|
+
{
|
|
285
|
+
feeType: ServiceBookingFeeType.CleaningFee,
|
|
286
|
+
name: "Cleaning Fee",
|
|
287
|
+
description: "Cleaning Fee",
|
|
288
|
+
costPQCents: cleaningFeePerBookingCents,
|
|
289
|
+
quantity: 1,
|
|
290
|
+
},
|
|
291
|
+
] as ServiceBookingFeeBase[]
|
|
292
|
+
).filter((fee) => fee.costPQCents > 0);
|
|
293
|
+
|
|
294
|
+
const daysToBookParams = bookingDateTimeRanges.map(
|
|
295
|
+
(dtRange): FrontendServiceBookingDayInfoParams => {
|
|
296
|
+
return {
|
|
297
|
+
dateTimeRange: dtRange,
|
|
298
|
+
fees: feesPerBooking,
|
|
299
|
+
addOns: addOns,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return frontendServiceGetPriceToBookFees(serviceRatesAssociation, {
|
|
305
|
+
daysToBookParams: daysToBookParams,
|
|
306
|
+
selectedAttendeeOption: selectedAttendeeOption,
|
|
307
|
+
additionalFees: [],
|
|
308
|
+
timezone: timezone,
|
|
309
|
+
});
|
|
310
|
+
}
|