@bash-app/bash-common 30.2.0 → 30.4.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/.vscode/settings.json +9 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +146 -93
- 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 +53 -21
- package/src/utils/stringUtils.ts +1 -1
- package/src/utils/service/serviceBookingApiUtils.ts +0 -259
- package/src/utils/service/serviceBookingUtils.ts +0 -391
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import { ServiceAddon } from "@prisma/client";
|
|
2
|
-
import { ServiceRatesAssociationExt } from "../../extendedSchemas";
|
|
3
|
-
import { ServiceAttendeeOption } from "./attendeeOptionUtils";
|
|
4
|
-
import {
|
|
5
|
-
getServiceRatePricingInfo,
|
|
6
|
-
serviceGetFilteredRates,
|
|
7
|
-
ServiceRatePricingInfo,
|
|
8
|
-
serviceRatesFilter,
|
|
9
|
-
serviceRatesHasRates,
|
|
10
|
-
ServiceRatesLuxon,
|
|
11
|
-
} from "./serviceRateUtils";
|
|
12
|
-
import {
|
|
13
|
-
dateTimeDiffHours,
|
|
14
|
-
dateTimeRangeHours,
|
|
15
|
-
LuxonDateRange,
|
|
16
|
-
} from "../luxonUtils";
|
|
17
|
-
|
|
18
|
-
export const SERVICE_BOOKING_PROCESSING_FEE_PERCENT = 0.15;
|
|
19
|
-
|
|
20
|
-
export interface ServiceAddonInput extends ServiceAddon {
|
|
21
|
-
chosenQuantity?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type ServiceBookingFeeType =
|
|
25
|
-
| "addOnFees"
|
|
26
|
-
| "cleaningFee"
|
|
27
|
-
| "processingFee";
|
|
28
|
-
|
|
29
|
-
export const ServiceBookingFeeType = {
|
|
30
|
-
addonFees: "addOnFees",
|
|
31
|
-
cleaningFee: "cleaningFee",
|
|
32
|
-
processingFee: "processingFee",
|
|
33
|
-
} as const;
|
|
34
|
-
|
|
35
|
-
export interface ServiceBookingFee {
|
|
36
|
-
name: string;
|
|
37
|
-
type: ServiceBookingFeeType;
|
|
38
|
-
fee: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ServiceBookingDayInfoParams {
|
|
42
|
-
dateTimeRange: LuxonDateRange;
|
|
43
|
-
fees: ServiceBookingFee[];
|
|
44
|
-
addOns: ServiceAddonInput[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ServiceBookingDayInfo {
|
|
48
|
-
priceBreakdown: ServiceRatePricingInfo[];
|
|
49
|
-
discounts: ServiceRatePricingInfo[];
|
|
50
|
-
|
|
51
|
-
dateTimeRange: LuxonDateRange;
|
|
52
|
-
|
|
53
|
-
baseCostUndiscounted: number; //rate purely based on generalHourly
|
|
54
|
-
baseCostDiscounted: number;
|
|
55
|
-
baseCostDiscount: number;
|
|
56
|
-
|
|
57
|
-
fees: ServiceBookingFee[];
|
|
58
|
-
totalBeforeTaxes: number;
|
|
59
|
-
|
|
60
|
-
addOns: ServiceAddonInput[];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface ServiceGetPriceToBookResult {
|
|
64
|
-
serviceId: string;
|
|
65
|
-
|
|
66
|
-
daysToBook: ServiceBookingDayInfo[];
|
|
67
|
-
|
|
68
|
-
baseCostUndiscounted: number; //rate purely based on generalHourly
|
|
69
|
-
baseCostDiscounted: number;
|
|
70
|
-
baseCostDiscount: number;
|
|
71
|
-
|
|
72
|
-
additionalFees: ServiceBookingFee[];
|
|
73
|
-
totalBeforeTaxes: number;
|
|
74
|
-
|
|
75
|
-
minimumTimeBlockHours?: number; //only valid on generalRate
|
|
76
|
-
totalDurationHours: number;
|
|
77
|
-
durationHoursPerDay: number;
|
|
78
|
-
|
|
79
|
-
baseHourlyRate: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface ServiceGetBookingDayInfoParams {
|
|
83
|
-
filteredRates: ServiceRatesLuxon;
|
|
84
|
-
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
85
|
-
|
|
86
|
-
timezone: string | null | undefined;
|
|
87
|
-
daysToBookParams: ServiceBookingDayInfoParams[];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function serviceGetBookingDayInfo({
|
|
91
|
-
filteredRates,
|
|
92
|
-
daysToBookParams,
|
|
93
|
-
// overlappingBusinessHours,
|
|
94
|
-
timezone,
|
|
95
|
-
}: ServiceGetBookingDayInfoParams): ServiceBookingDayInfo[] {
|
|
96
|
-
const bookingDayResults: ServiceBookingDayInfo[] = daysToBookParams.map(
|
|
97
|
-
(day) => {
|
|
98
|
-
const rates = serviceRatesFilter(filteredRates, [day.dateTimeRange]);
|
|
99
|
-
const { generalRate, specialRates, dailyRates } = rates;
|
|
100
|
-
|
|
101
|
-
let priceBreakdown: ServiceRatePricingInfo[] = [];
|
|
102
|
-
const durationHours = dateTimeDiffHours(
|
|
103
|
-
day.dateTimeRange.start,
|
|
104
|
-
day.dateTimeRange.end
|
|
105
|
-
);
|
|
106
|
-
// console.log(
|
|
107
|
-
// `serviceGetBookingDayInfo: durationHours: ${JSON.stringify(
|
|
108
|
-
// durationHours
|
|
109
|
-
// )}, dtRange: ${JSON.stringify(day.dateTimeRange)}`
|
|
110
|
-
// );
|
|
111
|
-
let hoursRemaining = durationHours;
|
|
112
|
-
let currentRange = { ...day.dateTimeRange };
|
|
113
|
-
let baseCostDiscounted = 0;
|
|
114
|
-
while (serviceRatesHasRates(rates) && hoursRemaining > 0) {
|
|
115
|
-
const pricingInfo = getServiceRatePricingInfo(
|
|
116
|
-
currentRange,
|
|
117
|
-
generalRate,
|
|
118
|
-
dailyRates,
|
|
119
|
-
specialRates,
|
|
120
|
-
// overlappingBusinessHours,
|
|
121
|
-
timezone,
|
|
122
|
-
hoursRemaining
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
if (pricingInfo.hours == 0 || pricingInfo.unitCount == 0) {
|
|
126
|
-
throw new Error(`Invalid pricingInfo`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
priceBreakdown.push(pricingInfo);
|
|
130
|
-
|
|
131
|
-
// currentRange.start = currentRange.start.plus({
|
|
132
|
-
// hours: pricingInfo.hours,
|
|
133
|
-
// });
|
|
134
|
-
currentRange.start = pricingInfo.dateTimeRange.end;
|
|
135
|
-
hoursRemaining -= pricingInfo.hours;
|
|
136
|
-
baseCostDiscounted += pricingInfo.piTotal;
|
|
137
|
-
|
|
138
|
-
if (pricingInfo.rateType == "Special") {
|
|
139
|
-
specialRates.pop();
|
|
140
|
-
} else if (pricingInfo.rateType == "Weekday") {
|
|
141
|
-
dailyRates.pop();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const baseHourlyRate = generalRate?.hourlyRate ?? 0;
|
|
146
|
-
|
|
147
|
-
const discounts = priceBreakdown
|
|
148
|
-
.filter(
|
|
149
|
-
(pi) => !(pi.pricingType == "Hourly" && pi.rateType == "General")
|
|
150
|
-
)
|
|
151
|
-
.map((pi) => {
|
|
152
|
-
return {
|
|
153
|
-
...pi,
|
|
154
|
-
piTotal: pi.hours * baseHourlyRate - pi.piTotal,
|
|
155
|
-
};
|
|
156
|
-
})
|
|
157
|
-
.filter((pi) => pi.piTotal > 0);
|
|
158
|
-
|
|
159
|
-
const baseCostUndiscounted = baseHourlyRate * durationHours;
|
|
160
|
-
const baseCostDiscount =
|
|
161
|
-
baseCostUndiscounted == 0
|
|
162
|
-
? 0
|
|
163
|
-
: 1 - baseCostDiscounted / baseCostUndiscounted;
|
|
164
|
-
|
|
165
|
-
const totalBeforeTaxes =
|
|
166
|
-
baseCostDiscounted +
|
|
167
|
-
day.fees.reduce((sofar, fee) => sofar + fee.fee, 0);
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
baseCostDiscount: baseCostDiscount,
|
|
171
|
-
baseCostDiscounted: baseCostDiscounted,
|
|
172
|
-
baseCostUndiscounted: baseCostUndiscounted,
|
|
173
|
-
discounts: discounts,
|
|
174
|
-
priceBreakdown: priceBreakdown,
|
|
175
|
-
dateTimeRange: day.dateTimeRange,
|
|
176
|
-
fees: day.fees,
|
|
177
|
-
addOns: day.addOns,
|
|
178
|
-
totalBeforeTaxes: totalBeforeTaxes,
|
|
179
|
-
} as ServiceBookingDayInfo;
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
return bookingDayResults;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export interface ServiceGetPriceToBookFeesParams {
|
|
187
|
-
daysToBookParams: ServiceBookingDayInfoParams[];
|
|
188
|
-
|
|
189
|
-
selectedAttendeeOption: ServiceAttendeeOption;
|
|
190
|
-
|
|
191
|
-
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
192
|
-
timezone: string | null | undefined;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function serviceGetPriceToBookFees(
|
|
196
|
-
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
197
|
-
{
|
|
198
|
-
daysToBookParams,
|
|
199
|
-
selectedAttendeeOption,
|
|
200
|
-
// overlappingBusinessHours,
|
|
201
|
-
timezone,
|
|
202
|
-
}: ServiceGetPriceToBookFeesParams
|
|
203
|
-
): ServiceGetPriceToBookResult {
|
|
204
|
-
const bookingDateTimeRanges = daysToBookParams.map(
|
|
205
|
-
(daysToBookParams) => daysToBookParams.dateTimeRange
|
|
206
|
-
);
|
|
207
|
-
const filteredRates = serviceGetFilteredRates(
|
|
208
|
-
serviceRatesAssociation,
|
|
209
|
-
bookingDateTimeRanges,
|
|
210
|
-
timezone
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
const bookingDayResults: ServiceBookingDayInfo[] = serviceGetBookingDayInfo({
|
|
214
|
-
filteredRates: filteredRates,
|
|
215
|
-
daysToBookParams: daysToBookParams,
|
|
216
|
-
// overlappingBusinessHours: overlappingBusinessHours,
|
|
217
|
-
timezone: timezone,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const durationHours = bookingDateTimeRanges.reduce((sofar, curr) => {
|
|
221
|
-
return sofar + dateTimeRangeHours(curr);
|
|
222
|
-
}, 0);
|
|
223
|
-
const bookingsTotalBeforeTaxes = bookingDayResults.reduce((sofar, curr) => {
|
|
224
|
-
return sofar + curr.totalBeforeTaxes;
|
|
225
|
-
}, 0);
|
|
226
|
-
const baseHourlyRate =
|
|
227
|
-
serviceRatesAssociation?.serviceGeneralRates?.hourlyRate ?? 0;
|
|
228
|
-
|
|
229
|
-
const baseCostUndiscounted = baseHourlyRate * durationHours;
|
|
230
|
-
const baseCostDiscount =
|
|
231
|
-
baseCostUndiscounted == 0
|
|
232
|
-
? 0
|
|
233
|
-
: 1 - bookingsTotalBeforeTaxes / baseCostUndiscounted;
|
|
234
|
-
|
|
235
|
-
const minimumTimeBlockHours =
|
|
236
|
-
serviceRatesAssociation?.serviceGeneralRates?.minimumTimeBlockHours;
|
|
237
|
-
|
|
238
|
-
const attendeeRate = selectedAttendeeOption.rate * durationHours;
|
|
239
|
-
|
|
240
|
-
const processingFee =
|
|
241
|
-
SERVICE_BOOKING_PROCESSING_FEE_PERCENT * bookingsTotalBeforeTaxes;
|
|
242
|
-
const additionalFees = (
|
|
243
|
-
[
|
|
244
|
-
{
|
|
245
|
-
name: "Processing Fee",
|
|
246
|
-
type: "processingFee",
|
|
247
|
-
fee: processingFee,
|
|
248
|
-
},
|
|
249
|
-
] as ServiceBookingFee[]
|
|
250
|
-
).filter((fee) => fee.fee > 0);
|
|
251
|
-
|
|
252
|
-
const totalBeforeTaxes =
|
|
253
|
-
bookingsTotalBeforeTaxes +
|
|
254
|
-
additionalFees.reduce((sofar, fee) => sofar + fee.fee, 0);
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
serviceId: serviceRatesAssociation?.serviceId,
|
|
258
|
-
daysToBook: bookingDayResults,
|
|
259
|
-
baseCostUndiscounted: baseCostUndiscounted,
|
|
260
|
-
baseCostDiscounted: bookingsTotalBeforeTaxes,
|
|
261
|
-
baseCostDiscount: baseCostDiscount,
|
|
262
|
-
totalBeforeTaxes: totalBeforeTaxes,
|
|
263
|
-
additionalFees: additionalFees,
|
|
264
|
-
baseHourlyRate: baseHourlyRate,
|
|
265
|
-
totalDurationHours: durationHours,
|
|
266
|
-
minimumTimeBlockHours: minimumTimeBlockHours,
|
|
267
|
-
durationHoursPerDay: dateTimeRangeHours(bookingDateTimeRanges[0]),
|
|
268
|
-
} as ServiceGetPriceToBookResult;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export interface ServiceGetPriceToBookParams {
|
|
272
|
-
addOns: ServiceAddonInput[];
|
|
273
|
-
|
|
274
|
-
selectedAttendeeOption: ServiceAttendeeOption;
|
|
275
|
-
|
|
276
|
-
bookingDateTimeRanges: LuxonDateRange[];
|
|
277
|
-
|
|
278
|
-
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
279
|
-
timezone: string | null | undefined;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export function serviceGetPriceToBook(
|
|
283
|
-
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
284
|
-
{
|
|
285
|
-
addOns,
|
|
286
|
-
selectedAttendeeOption,
|
|
287
|
-
bookingDateTimeRanges, //list of dateTime ranges to book
|
|
288
|
-
// overlappingBusinessHours,
|
|
289
|
-
timezone,
|
|
290
|
-
}: ServiceGetPriceToBookParams
|
|
291
|
-
): ServiceGetPriceToBookResult {
|
|
292
|
-
const cleaningFee =
|
|
293
|
-
serviceRatesAssociation?.serviceGeneralRates?.cleaningFeePerBooking ?? 0;
|
|
294
|
-
|
|
295
|
-
const addOnTotal = addOns.reduce((sum, addOn) => {
|
|
296
|
-
return sum + (addOn.chosenQuantity || 0) * addOn.price;
|
|
297
|
-
}, 0);
|
|
298
|
-
|
|
299
|
-
const feesPerBooking = (
|
|
300
|
-
[
|
|
301
|
-
{
|
|
302
|
-
name: "AddOn Total",
|
|
303
|
-
type: "addOnFees",
|
|
304
|
-
fee: addOnTotal,
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
name: "Cleaning Fee",
|
|
308
|
-
type: "cleaningFee",
|
|
309
|
-
fee: cleaningFee,
|
|
310
|
-
},
|
|
311
|
-
] as ServiceBookingFee[]
|
|
312
|
-
).filter((fee) => fee.fee > 0);
|
|
313
|
-
|
|
314
|
-
const daysToBookParams = bookingDateTimeRanges.map(
|
|
315
|
-
(dtRange): ServiceBookingDayInfoParams => {
|
|
316
|
-
return {
|
|
317
|
-
dateTimeRange: dtRange,
|
|
318
|
-
fees: feesPerBooking,
|
|
319
|
-
addOns: addOns,
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
return serviceGetPriceToBookFees(serviceRatesAssociation, {
|
|
325
|
-
daysToBookParams: daysToBookParams,
|
|
326
|
-
selectedAttendeeOption: selectedAttendeeOption,
|
|
327
|
-
timezone: timezone,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export interface ServiceGetPriceToBookExtDay {
|
|
332
|
-
dateRange: LuxonDateRange;
|
|
333
|
-
addOns: ServiceAddonInput[];
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export interface ServiceGetPriceToBookExtParams {
|
|
337
|
-
selectedAttendeeOption: ServiceAttendeeOption;
|
|
338
|
-
|
|
339
|
-
bookedDays: ServiceGetPriceToBookExtDay[];
|
|
340
|
-
|
|
341
|
-
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
342
|
-
timezone: string | null | undefined;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function serviceGetPriceToBookExt(
|
|
346
|
-
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
347
|
-
{
|
|
348
|
-
selectedAttendeeOption,
|
|
349
|
-
bookedDays, //list of dateTime ranges to book
|
|
350
|
-
// overlappingBusinessHours,
|
|
351
|
-
timezone,
|
|
352
|
-
}: ServiceGetPriceToBookExtParams
|
|
353
|
-
): ServiceGetPriceToBookResult {
|
|
354
|
-
const cleaningFee =
|
|
355
|
-
serviceRatesAssociation?.serviceGeneralRates?.cleaningFeePerBooking ?? 0;
|
|
356
|
-
|
|
357
|
-
const daysToBookParams = bookedDays.map(
|
|
358
|
-
(day): ServiceBookingDayInfoParams => {
|
|
359
|
-
const addOnTotal = day.addOns.reduce((sum, addOn) => {
|
|
360
|
-
return sum + (addOn.chosenQuantity || 0) * addOn.price;
|
|
361
|
-
}, 0);
|
|
362
|
-
|
|
363
|
-
const feesPerBooking = (
|
|
364
|
-
[
|
|
365
|
-
{
|
|
366
|
-
name: "AddOn Total",
|
|
367
|
-
type: "addOnFees",
|
|
368
|
-
fee: addOnTotal,
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
name: "Cleaning Fee",
|
|
372
|
-
type: "cleaningFee",
|
|
373
|
-
fee: cleaningFee,
|
|
374
|
-
},
|
|
375
|
-
] as ServiceBookingFee[]
|
|
376
|
-
).filter((fee) => fee.fee > 0);
|
|
377
|
-
|
|
378
|
-
return {
|
|
379
|
-
dateTimeRange: day.dateRange,
|
|
380
|
-
fees: feesPerBooking,
|
|
381
|
-
addOns: day.addOns,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
return serviceGetPriceToBookFees(serviceRatesAssociation, {
|
|
387
|
-
daysToBookParams: daysToBookParams,
|
|
388
|
-
selectedAttendeeOption: selectedAttendeeOption,
|
|
389
|
-
timezone: timezone,
|
|
390
|
-
});
|
|
391
|
-
}
|