@bash-app/bash-common 29.66.0 → 29.68.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 +3 -1
- package/prisma/schema.prisma +217 -67
- package/src/definitions.ts +131 -60
- package/src/extendedSchemas.ts +166 -111
- package/src/index.ts +9 -0
- package/src/utils/dateTimeUtils.ts +120 -60
- package/src/utils/generalDateTimeUtils.ts +43 -0
- package/src/utils/luxonUtils.ts +871 -72
- package/src/utils/mathUtils.ts +3 -0
- package/src/utils/service/attendeeOptionUtils.ts +19 -0
- package/src/utils/service/serviceBookingApiUtils.ts +116 -0
- package/src/utils/service/serviceBookingUtils.ts +321 -0
- package/src/utils/service/serviceDBUtils.ts +51 -0
- package/src/utils/service/serviceRateDBUtils.ts +179 -0
- package/src/utils/service/serviceRateUtils.ts +350 -0
- package/src/utils/service/serviceUtils.ts +35 -0
- package/src/utils/typeUtils.ts +16 -0
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ServiceAttendeeOptionRange {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ServiceAttendeeOption {
|
|
7
|
+
label: string;
|
|
8
|
+
rate: number;
|
|
9
|
+
personRange: ServiceAttendeeOptionRange;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const serviceAttendeeOptions = [
|
|
13
|
+
{ label: "1 - 5 people", personRange: { start: 1, end: 5 }, rate: 0 },
|
|
14
|
+
{ label: "6 - 15 people", personRange: { start: 6, end: 15 }, rate: 50 },
|
|
15
|
+
{ label: "16 - 30 people", personRange: { start: 16, end: 30 }, rate: 100 },
|
|
16
|
+
{ label: "31 - 45 people", personRange: { start: 31, end: 45 }, rate: 200 },
|
|
17
|
+
{ label: "46 - 60 people", personRange: { start: 46, end: 60 }, rate: 400 },
|
|
18
|
+
{ label: "61 - 75 people", personRange: { start: 61, end: 75 }, rate: 500 },
|
|
19
|
+
] as ServiceAttendeeOption[];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServiceBookingApiParamsLuxon,
|
|
3
|
+
ServiceBookingApiParams,
|
|
4
|
+
ServiceBookedDayApiParamsLuxon,
|
|
5
|
+
} from "../../definitions";
|
|
6
|
+
import { ServiceAddonExt } from "../../extendedSchemas";
|
|
7
|
+
import { dateTimeFromString } from "../luxonUtils";
|
|
8
|
+
import { ServiceAttendeeOption } from "./attendeeOptionUtils";
|
|
9
|
+
import {
|
|
10
|
+
ServiceGetPriceToBookFeesParams,
|
|
11
|
+
ServiceBookingDayInfoParams,
|
|
12
|
+
ServiceBookingFee,
|
|
13
|
+
} from "./serviceBookingUtils";
|
|
14
|
+
|
|
15
|
+
export type ServiceCantBookReason = {
|
|
16
|
+
type: string;
|
|
17
|
+
msg: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const ServiceCantBookReasons = {
|
|
21
|
+
notInBusinessHours: {
|
|
22
|
+
type: "notInBusinessHours",
|
|
23
|
+
msg: "Time not within normal Business Hours",
|
|
24
|
+
} as ServiceCantBookReason,
|
|
25
|
+
minimumTimeBlock: {
|
|
26
|
+
type: "minimumTimeBlock",
|
|
27
|
+
msg: "Minimum time block not satisfied",
|
|
28
|
+
} as ServiceCantBookReason,
|
|
29
|
+
dateBlocked: {
|
|
30
|
+
type: "dateBlocked",
|
|
31
|
+
msg: "The selected date is blocked.",
|
|
32
|
+
} as ServiceCantBookReason,
|
|
33
|
+
alreadyBooked: {
|
|
34
|
+
type: "alreadyBooked",
|
|
35
|
+
msg: "This service has already been booked.",
|
|
36
|
+
} as ServiceCantBookReason,
|
|
37
|
+
requestDeclined: {
|
|
38
|
+
type: "requestDeclined",
|
|
39
|
+
msg: "The booking request has been declined.",
|
|
40
|
+
} as ServiceCantBookReason,
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
export type ServiceCantBookReasons = keyof typeof ServiceCantBookReasons;
|
|
44
|
+
|
|
45
|
+
export function serviceBookingParamsToPriceToBookParams(
|
|
46
|
+
params: ServiceBookingApiParamsLuxon,
|
|
47
|
+
serviceAddons: ServiceAddonExt[]
|
|
48
|
+
): ServiceGetPriceToBookFeesParams {
|
|
49
|
+
// Create a HashMap (Map) for faster lookup by addonId
|
|
50
|
+
const addonMap = new Map<string, ServiceAddonExt>(
|
|
51
|
+
serviceAddons.map((addon) => [addon.id, addon])
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Map the addOns from the params to the format expected by ServiceGetPriceToBookFeesParams
|
|
55
|
+
const daysToBookParams: ServiceBookingDayInfoParams[] = params.bookedDays.map(
|
|
56
|
+
(day) => {
|
|
57
|
+
// Map over each day's addOns and match with the addonMap to get additional data
|
|
58
|
+
const addOnsWithDetails = day.addOns.map((addonInput) => {
|
|
59
|
+
const matchedAddon = addonMap.get(addonInput.addonId);
|
|
60
|
+
|
|
61
|
+
if (!matchedAddon) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Failed to match addon with id: ${addonInput.addonId}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...matchedAddon,
|
|
69
|
+
addonId: addonInput.addonId,
|
|
70
|
+
chosenQuantity: addonInput.chosenQuantity,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
dateTimeRange: day.dateTimeRange,
|
|
76
|
+
fees: addOnsWithDetails.map(
|
|
77
|
+
(addon): ServiceBookingFee => ({
|
|
78
|
+
name: `${addon.name} Fee`,
|
|
79
|
+
type: "addOnFees",
|
|
80
|
+
fee: (addon.chosenQuantity || 0) * (addon.price || 0),
|
|
81
|
+
})
|
|
82
|
+
),
|
|
83
|
+
} as ServiceBookingDayInfoParams;
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
daysToBookParams: daysToBookParams,
|
|
89
|
+
selectedAttendeeOption: {} as ServiceAttendeeOption, // Placeholder, should be determined based on business logic
|
|
90
|
+
timezone: params.timezone,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function serviceBookingParamsToLuxon(
|
|
95
|
+
params: ServiceBookingApiParams
|
|
96
|
+
): ServiceBookingApiParamsLuxon {
|
|
97
|
+
const bookedDays = params.bookedDays.reduce(
|
|
98
|
+
(sofar, day): ServiceBookedDayApiParamsLuxon[] => {
|
|
99
|
+
sofar.push({
|
|
100
|
+
...day,
|
|
101
|
+
dateTimeRange: {
|
|
102
|
+
start: dateTimeFromString(day.startDate),
|
|
103
|
+
end: dateTimeFromString(day.endDate),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return sofar;
|
|
108
|
+
},
|
|
109
|
+
[] as ServiceBookedDayApiParamsLuxon[]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...params,
|
|
114
|
+
bookedDays,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
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 interface ServiceAddonInput extends ServiceAddon {
|
|
19
|
+
chosenQuantity?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ServiceBookingFeeType =
|
|
23
|
+
| "addOnFees"
|
|
24
|
+
| "cleaningFee"
|
|
25
|
+
| "processingFee";
|
|
26
|
+
|
|
27
|
+
export const ServiceBookingFeeType = {
|
|
28
|
+
addonFees: "addOnFees",
|
|
29
|
+
cleaningFee: "cleaningFee",
|
|
30
|
+
processingFee: "processingFee",
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export interface ServiceBookingFee {
|
|
34
|
+
name: string;
|
|
35
|
+
type: ServiceBookingFeeType;
|
|
36
|
+
fee: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ServiceBookingDayInfoParams {
|
|
40
|
+
dateTimeRange: LuxonDateRange;
|
|
41
|
+
fees: ServiceBookingFee[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ServiceBookingDayInfo {
|
|
45
|
+
priceBreakdown: ServiceRatePricingInfo[];
|
|
46
|
+
discounts: ServiceRatePricingInfo[];
|
|
47
|
+
|
|
48
|
+
dateTimeRange: LuxonDateRange;
|
|
49
|
+
|
|
50
|
+
baseCostUndiscounted: number; //rate purely based on generalHourly
|
|
51
|
+
baseCostDiscounted: number;
|
|
52
|
+
baseCostDiscount: number;
|
|
53
|
+
|
|
54
|
+
fees: ServiceBookingFee[];
|
|
55
|
+
totalBeforeTaxes: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ServiceGetPriceToBookResult {
|
|
59
|
+
serviceId: string;
|
|
60
|
+
|
|
61
|
+
daysToBook: ServiceBookingDayInfo[];
|
|
62
|
+
|
|
63
|
+
baseCostUndiscounted: number; //rate purely based on generalHourly
|
|
64
|
+
baseCostDiscounted: number;
|
|
65
|
+
baseCostDiscount: number;
|
|
66
|
+
|
|
67
|
+
additionalFees: ServiceBookingFee[];
|
|
68
|
+
totalBeforeTaxes: number;
|
|
69
|
+
|
|
70
|
+
minimumTimeBlockHours?: number; //only valid on generalRate
|
|
71
|
+
totalDurationHours: number;
|
|
72
|
+
durationHoursPerDay: number;
|
|
73
|
+
|
|
74
|
+
baseHourlyRate: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ServiceGetBookingDayInfoParams {
|
|
78
|
+
filteredRates: ServiceRatesLuxon;
|
|
79
|
+
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
80
|
+
|
|
81
|
+
timezone: string | null | undefined;
|
|
82
|
+
daysToBookParams: ServiceBookingDayInfoParams[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function serviceGetBookingDayInfo({
|
|
86
|
+
filteredRates,
|
|
87
|
+
daysToBookParams,
|
|
88
|
+
// overlappingBusinessHours,
|
|
89
|
+
timezone,
|
|
90
|
+
}: ServiceGetBookingDayInfoParams): ServiceBookingDayInfo[] {
|
|
91
|
+
const bookingDayResults: ServiceBookingDayInfo[] = daysToBookParams.map(
|
|
92
|
+
(day) => {
|
|
93
|
+
const rates = serviceRatesFilter(filteredRates, [day.dateTimeRange]);
|
|
94
|
+
const { generalRate, specialRates, dailyRates } = rates;
|
|
95
|
+
|
|
96
|
+
let priceBreakdown: ServiceRatePricingInfo[] = [];
|
|
97
|
+
const durationHours = dateTimeDiffHours(
|
|
98
|
+
day.dateTimeRange.start,
|
|
99
|
+
day.dateTimeRange.end
|
|
100
|
+
);
|
|
101
|
+
// console.log(
|
|
102
|
+
// `serviceGetBookingDayInfo: durationHours: ${JSON.stringify(
|
|
103
|
+
// durationHours
|
|
104
|
+
// )}, dtRange: ${JSON.stringify(day.dateTimeRange)}`
|
|
105
|
+
// );
|
|
106
|
+
let hoursRemaining = durationHours;
|
|
107
|
+
let currentRange = { ...day.dateTimeRange };
|
|
108
|
+
let baseCostDiscounted = 0;
|
|
109
|
+
while (serviceRatesHasRates(rates) && hoursRemaining > 0) {
|
|
110
|
+
const pricingInfo = getServiceRatePricingInfo(
|
|
111
|
+
currentRange,
|
|
112
|
+
generalRate,
|
|
113
|
+
dailyRates,
|
|
114
|
+
specialRates,
|
|
115
|
+
// overlappingBusinessHours,
|
|
116
|
+
timezone,
|
|
117
|
+
hoursRemaining
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (pricingInfo.hours == 0 || pricingInfo.unitCount == 0) {
|
|
121
|
+
throw new Error(`Invalid pricingInfo`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
priceBreakdown.push(pricingInfo);
|
|
125
|
+
|
|
126
|
+
// currentRange.start = currentRange.start.plus({
|
|
127
|
+
// hours: pricingInfo.hours,
|
|
128
|
+
// });
|
|
129
|
+
currentRange.start = pricingInfo.dateTimeRange.end;
|
|
130
|
+
hoursRemaining -= pricingInfo.hours;
|
|
131
|
+
baseCostDiscounted += pricingInfo.piTotal;
|
|
132
|
+
|
|
133
|
+
if (pricingInfo.rateType == "Special") {
|
|
134
|
+
specialRates.pop();
|
|
135
|
+
} else if (pricingInfo.rateType == "Weekday") {
|
|
136
|
+
dailyRates.pop();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const baseHourlyRate = generalRate?.hourlyRate ?? 0;
|
|
141
|
+
|
|
142
|
+
const discounts = priceBreakdown
|
|
143
|
+
.filter(
|
|
144
|
+
(pi) => !(pi.pricingType == "Hourly" && pi.rateType == "General")
|
|
145
|
+
)
|
|
146
|
+
.map((pi) => {
|
|
147
|
+
return {
|
|
148
|
+
...pi,
|
|
149
|
+
piTotal: pi.hours * baseHourlyRate - pi.piTotal,
|
|
150
|
+
};
|
|
151
|
+
})
|
|
152
|
+
.filter((pi) => pi.piTotal > 0);
|
|
153
|
+
|
|
154
|
+
const baseCostUndiscounted = baseHourlyRate * durationHours;
|
|
155
|
+
const baseCostDiscount =
|
|
156
|
+
baseCostUndiscounted == 0
|
|
157
|
+
? 0
|
|
158
|
+
: 1 - baseCostDiscounted / baseCostUndiscounted;
|
|
159
|
+
|
|
160
|
+
const totalBeforeTaxes =
|
|
161
|
+
baseCostDiscounted +
|
|
162
|
+
day.fees.reduce((sofar, fee) => sofar + fee.fee, 0);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
baseCostDiscount: baseCostDiscount,
|
|
166
|
+
baseCostDiscounted: baseCostDiscounted,
|
|
167
|
+
baseCostUndiscounted: baseCostUndiscounted,
|
|
168
|
+
discounts: discounts,
|
|
169
|
+
priceBreakdown: priceBreakdown,
|
|
170
|
+
dateTimeRange: day.dateTimeRange,
|
|
171
|
+
fees: day.fees,
|
|
172
|
+
totalBeforeTaxes: totalBeforeTaxes,
|
|
173
|
+
} as ServiceBookingDayInfo;
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
return bookingDayResults;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ServiceGetPriceToBookFeesParams {
|
|
181
|
+
daysToBookParams: ServiceBookingDayInfoParams[];
|
|
182
|
+
|
|
183
|
+
selectedAttendeeOption: ServiceAttendeeOption;
|
|
184
|
+
|
|
185
|
+
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
186
|
+
timezone: string | null | undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function serviceGetPriceToBookFees(
|
|
190
|
+
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
191
|
+
{
|
|
192
|
+
daysToBookParams,
|
|
193
|
+
selectedAttendeeOption,
|
|
194
|
+
// overlappingBusinessHours,
|
|
195
|
+
timezone,
|
|
196
|
+
}: ServiceGetPriceToBookFeesParams
|
|
197
|
+
): ServiceGetPriceToBookResult {
|
|
198
|
+
const bookingDateTimeRanges = daysToBookParams.map(
|
|
199
|
+
(daysToBookParams) => daysToBookParams.dateTimeRange
|
|
200
|
+
);
|
|
201
|
+
const filteredRates = serviceGetFilteredRates(
|
|
202
|
+
serviceRatesAssociation,
|
|
203
|
+
bookingDateTimeRanges,
|
|
204
|
+
timezone
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const processingFee = 53.08;
|
|
208
|
+
const additionalFees = (
|
|
209
|
+
[
|
|
210
|
+
{
|
|
211
|
+
name: "Processing Fee",
|
|
212
|
+
type: "processingFee",
|
|
213
|
+
fee: processingFee,
|
|
214
|
+
},
|
|
215
|
+
] as ServiceBookingFee[]
|
|
216
|
+
).filter((fee) => fee.fee > 0);
|
|
217
|
+
|
|
218
|
+
const bookingDayResults: ServiceBookingDayInfo[] = serviceGetBookingDayInfo({
|
|
219
|
+
filteredRates: filteredRates,
|
|
220
|
+
daysToBookParams: daysToBookParams,
|
|
221
|
+
// overlappingBusinessHours: overlappingBusinessHours,
|
|
222
|
+
timezone: timezone,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const durationHours = bookingDateTimeRanges.reduce((sofar, curr) => {
|
|
226
|
+
return sofar + dateTimeRangeHours(curr);
|
|
227
|
+
}, 0);
|
|
228
|
+
const baseCostDiscounted = bookingDayResults.reduce((sofar, curr) => {
|
|
229
|
+
return sofar + curr.baseCostDiscounted;
|
|
230
|
+
}, 0);
|
|
231
|
+
const baseHourlyRate =
|
|
232
|
+
serviceRatesAssociation?.serviceGeneralRates?.hourlyRate ?? 0;
|
|
233
|
+
|
|
234
|
+
const baseCostUndiscounted = baseHourlyRate * durationHours;
|
|
235
|
+
const baseCostDiscount =
|
|
236
|
+
baseCostUndiscounted == 0
|
|
237
|
+
? 0
|
|
238
|
+
: 1 - baseCostDiscounted / baseCostUndiscounted;
|
|
239
|
+
|
|
240
|
+
const minimumTimeBlockHours =
|
|
241
|
+
serviceRatesAssociation?.serviceGeneralRates?.minimumTimeBlockHours;
|
|
242
|
+
|
|
243
|
+
const attendeeRate = selectedAttendeeOption.rate * durationHours;
|
|
244
|
+
|
|
245
|
+
const totalBeforeTaxes =
|
|
246
|
+
baseCostDiscounted +
|
|
247
|
+
additionalFees.reduce((sofar, fee) => sofar + fee.fee, 0);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
serviceId: serviceRatesAssociation?.serviceId,
|
|
251
|
+
daysToBook: bookingDayResults,
|
|
252
|
+
baseCostUndiscounted: baseCostUndiscounted,
|
|
253
|
+
baseCostDiscounted: baseCostDiscounted,
|
|
254
|
+
baseCostDiscount: baseCostDiscount,
|
|
255
|
+
totalBeforeTaxes: totalBeforeTaxes,
|
|
256
|
+
additionalFees: additionalFees,
|
|
257
|
+
baseHourlyRate: baseHourlyRate,
|
|
258
|
+
totalDurationHours: durationHours,
|
|
259
|
+
minimumTimeBlockHours: minimumTimeBlockHours,
|
|
260
|
+
durationHoursPerDay: dateTimeRangeHours(bookingDateTimeRanges[0]),
|
|
261
|
+
} as ServiceGetPriceToBookResult;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface ServiceGetPriceToBookParams {
|
|
265
|
+
addOns: ServiceAddonInput[];
|
|
266
|
+
|
|
267
|
+
selectedAttendeeOption: ServiceAttendeeOption;
|
|
268
|
+
|
|
269
|
+
bookingDateTimeRanges: LuxonDateRange[];
|
|
270
|
+
|
|
271
|
+
// overlappingBusinessHours: LuxonDateRange[] | null;
|
|
272
|
+
timezone: string | null | undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function serviceGetPriceToBook(
|
|
276
|
+
serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
|
|
277
|
+
{
|
|
278
|
+
addOns,
|
|
279
|
+
selectedAttendeeOption,
|
|
280
|
+
bookingDateTimeRanges, //list of dateTime ranges to book
|
|
281
|
+
// overlappingBusinessHours,
|
|
282
|
+
timezone,
|
|
283
|
+
}: ServiceGetPriceToBookParams
|
|
284
|
+
): ServiceGetPriceToBookResult {
|
|
285
|
+
const cleaningFee =
|
|
286
|
+
serviceRatesAssociation?.serviceGeneralRates?.cleaningFeePerBooking ?? 0;
|
|
287
|
+
|
|
288
|
+
const addOnTotal = addOns.reduce((sum, addOn) => {
|
|
289
|
+
return sum + (addOn.chosenQuantity || 0) * addOn.price;
|
|
290
|
+
}, 0);
|
|
291
|
+
|
|
292
|
+
const feesPerBooking = (
|
|
293
|
+
[
|
|
294
|
+
{
|
|
295
|
+
name: "AddOn Total",
|
|
296
|
+
type: "addOnFees",
|
|
297
|
+
fee: addOnTotal,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "Cleaning Fee",
|
|
301
|
+
type: "cleaningFee",
|
|
302
|
+
fee: cleaningFee,
|
|
303
|
+
},
|
|
304
|
+
] as ServiceBookingFee[]
|
|
305
|
+
).filter((fee) => fee.fee > 0);
|
|
306
|
+
|
|
307
|
+
const daysToBookParams = bookingDateTimeRanges.map(
|
|
308
|
+
(dtRange): ServiceBookingDayInfoParams => {
|
|
309
|
+
return {
|
|
310
|
+
dateTimeRange: dtRange,
|
|
311
|
+
fees: feesPerBooking,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return serviceGetPriceToBookFees(serviceRatesAssociation, {
|
|
317
|
+
daysToBookParams: daysToBookParams,
|
|
318
|
+
selectedAttendeeOption: selectedAttendeeOption,
|
|
319
|
+
timezone: timezone,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ServiceRate } from "@prisma/client";
|
|
2
|
+
import { ServiceExt, ServiceSpecialRatesExt } from "../../extendedSchemas";
|
|
3
|
+
import {
|
|
4
|
+
specialRateConvertToDb,
|
|
5
|
+
generalRatesConvertRatesToDB,
|
|
6
|
+
specialRateConvertFromDb,
|
|
7
|
+
generalRatesConvertRatesFromDB,
|
|
8
|
+
} from "./serviceRateDBUtils";
|
|
9
|
+
|
|
10
|
+
export function serviceToDb(service: Partial<ServiceExt>): Partial<ServiceExt> {
|
|
11
|
+
if (service.serviceRatesAssociation) {
|
|
12
|
+
service.serviceRatesAssociation.serviceSpecialRates =
|
|
13
|
+
service.serviceRatesAssociation.serviceSpecialRates?.map(
|
|
14
|
+
(
|
|
15
|
+
serviceSpecialRate: Partial<ServiceSpecialRatesExt>
|
|
16
|
+
): ServiceSpecialRatesExt => {
|
|
17
|
+
return specialRateConvertToDb(
|
|
18
|
+
serviceSpecialRate
|
|
19
|
+
) as ServiceSpecialRatesExt;
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
if (service.serviceRatesAssociation.serviceGeneralRates) {
|
|
23
|
+
service.serviceRatesAssociation.serviceGeneralRates =
|
|
24
|
+
generalRatesConvertRatesToDB(
|
|
25
|
+
service.serviceRatesAssociation.serviceGeneralRates
|
|
26
|
+
) as ServiceRate;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return service;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function serviceFromDb(service: ServiceExt): ServiceExt {
|
|
33
|
+
if (service.serviceRatesAssociation) {
|
|
34
|
+
service.serviceRatesAssociation.serviceSpecialRates =
|
|
35
|
+
service.serviceRatesAssociation.serviceSpecialRates?.map(
|
|
36
|
+
(
|
|
37
|
+
serviceSpecialRate: ServiceSpecialRatesExt
|
|
38
|
+
): ServiceSpecialRatesExt => {
|
|
39
|
+
return specialRateConvertFromDb(serviceSpecialRate);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
if (service.serviceRatesAssociation.serviceGeneralRates) {
|
|
43
|
+
console.log("updating general rates from db");
|
|
44
|
+
service.serviceRatesAssociation.serviceGeneralRates =
|
|
45
|
+
generalRatesConvertRatesFromDB(
|
|
46
|
+
service.serviceRatesAssociation.serviceGeneralRates
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return service;
|
|
51
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { ServiceRate } from "@prisma/client";
|
|
2
|
+
import { ApiResult } from "../../definitions";
|
|
3
|
+
import { ServiceSpecialRatesExt } from "../../extendedSchemas";
|
|
4
|
+
import { convertDollarsToCents, convertCentsToDollars } from "../paymentUtils";
|
|
5
|
+
|
|
6
|
+
export function specialRateConvertToDb(
|
|
7
|
+
specialRate: Partial<ServiceSpecialRatesExt>
|
|
8
|
+
): Partial<ServiceSpecialRatesExt> {
|
|
9
|
+
return specialRateConvertRatesToDB(
|
|
10
|
+
specialRateConvertDateStringsToDb(structuredClone(specialRate))
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function specialRateConvertFromDb(
|
|
15
|
+
specialRate: ServiceSpecialRatesExt
|
|
16
|
+
): ServiceSpecialRatesExt {
|
|
17
|
+
// console.log(`specialRateConvertFromDb`);
|
|
18
|
+
return specialRateConvertRatesFromDB(
|
|
19
|
+
specialRateConvertDateStringsFromDb(specialRate)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function specialRateConvertDateStringsToDb(
|
|
24
|
+
specialRate: Partial<ServiceSpecialRatesExt>
|
|
25
|
+
): Partial<ServiceSpecialRatesExt> {
|
|
26
|
+
// const oldStart = new Date(specialRate.startDate!);
|
|
27
|
+
// const oldEnd = new Date(specialRate.endDate!);
|
|
28
|
+
|
|
29
|
+
// specialRate.startDate = dateTimeLocalToUTCDate(specialRate.startDate!);
|
|
30
|
+
// specialRate.endDate = dateTimeLocalToUTCDate(specialRate.endDate!);
|
|
31
|
+
// specialRate.startDate = normalizeDate(specialRate.startDate!);
|
|
32
|
+
// specialRate.endDate = normalizeDate(specialRate.endDate!);
|
|
33
|
+
|
|
34
|
+
// const localStart = dateTimeLocalFromUTCDate(new Date(specialRate.startDate!));
|
|
35
|
+
// const localEnd = dateTimeLocalFromUTCDate(new Date(specialRate.endDate!));
|
|
36
|
+
|
|
37
|
+
// console.log(
|
|
38
|
+
// `specialRateConvertDateStringsToDb oldStart: ${oldStart?.toString()}`
|
|
39
|
+
// );
|
|
40
|
+
// console.log(
|
|
41
|
+
// `specialRateConvertDateStringsToDb oldEnd: ${oldEnd?.toString()}`
|
|
42
|
+
// );
|
|
43
|
+
// console.log(
|
|
44
|
+
// `specialRateConvertDateStringsToDb specialRate.startDate: ${specialRate.startDate?.toString()}`
|
|
45
|
+
// );
|
|
46
|
+
// console.log(
|
|
47
|
+
// `specialRateConvertDateStringsToDb specialRate.endDate: ${specialRate.endDate?.toString()}`
|
|
48
|
+
// );
|
|
49
|
+
// console.log(
|
|
50
|
+
// `specialRateConvertDateStringsToDb localStart: ${localStart?.toString()}`
|
|
51
|
+
// );
|
|
52
|
+
// console.log(
|
|
53
|
+
// `specialRateConvertDateStringsToDb localEnd: ${localEnd?.toString()}`
|
|
54
|
+
// );
|
|
55
|
+
|
|
56
|
+
// const a = 3 + 4;
|
|
57
|
+
|
|
58
|
+
// specialRate.startDate = specialRate.startDate.toISOString();
|
|
59
|
+
// specialRate.endDate = specialRate.endDate.toISOString();
|
|
60
|
+
// return specialRate;
|
|
61
|
+
return specialRate;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function specialRateConvertDateStringsFromDb(
|
|
65
|
+
specialRate: ServiceSpecialRatesExt
|
|
66
|
+
): ServiceSpecialRatesExt {
|
|
67
|
+
// specialRate.startDate = localDateToUTC(new Date(specialRate.startDate));
|
|
68
|
+
// specialRate.endDate = localDateToUTC(new Date(specialRate.endDate));
|
|
69
|
+
|
|
70
|
+
//this shouldnt even be necessary as javascript dates are always stored in UTC timezone (they lack timezone information)
|
|
71
|
+
// specialRate.startDate = ensureDateTimeIsLocalDateTime(
|
|
72
|
+
// new Date(specialRate.startDate)
|
|
73
|
+
// ).toDate();
|
|
74
|
+
// specialRate.endDate = ensureDateTimeIsLocalDateTime(
|
|
75
|
+
// new Date(specialRate.endDate)
|
|
76
|
+
// ).toDate();
|
|
77
|
+
|
|
78
|
+
specialRate.startDate = new Date(specialRate.startDate);
|
|
79
|
+
specialRate.endDate = new Date(specialRate.endDate);
|
|
80
|
+
|
|
81
|
+
// const oldStart = new Date(specialRate.startDate!);
|
|
82
|
+
// const oldEnd = new Date(specialRate.endDate!);
|
|
83
|
+
|
|
84
|
+
// specialRate.startDate = dateTimeLocalFromUTCDate(
|
|
85
|
+
// new Date(specialRate.startDate)
|
|
86
|
+
// );
|
|
87
|
+
// specialRate.endDate = dateTimeLocalFromUTCDate(new Date(specialRate.endDate));
|
|
88
|
+
|
|
89
|
+
// // specialRate.startDate = ensureDateTimeIsLocalDateTime(
|
|
90
|
+
// // new Date(specialRate.startDate)
|
|
91
|
+
// // ).toDate();
|
|
92
|
+
// // specialRate.endDate = ensureDateTimeIsLocalDateTime(
|
|
93
|
+
// // new Date(specialRate.endDate)
|
|
94
|
+
// // ).toDate();
|
|
95
|
+
|
|
96
|
+
// // specialRate.startDate = localDateToUTC(new Date(specialRate.startDate));
|
|
97
|
+
// // specialRate.endDate = localDateToUTC(new Date(specialRate.endDate));
|
|
98
|
+
|
|
99
|
+
// const date2 = normalizeDate(specialRate.startDate)!;
|
|
100
|
+
// const date3 = normalizeDate(specialRate.endDate)!;
|
|
101
|
+
|
|
102
|
+
// const a = 3 + 4;
|
|
103
|
+
return specialRate;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function specialRateConvertRatesToDB(
|
|
107
|
+
specialRate: Partial<ServiceSpecialRatesExt>
|
|
108
|
+
): Partial<ServiceSpecialRatesExt> {
|
|
109
|
+
if (specialRate.serviceRate) {
|
|
110
|
+
specialRate.serviceRate = generalRatesConvertRatesToDB(
|
|
111
|
+
specialRate.serviceRate
|
|
112
|
+
) as ServiceRate;
|
|
113
|
+
}
|
|
114
|
+
return specialRate;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function specialRateConvertRatesFromDB(
|
|
118
|
+
specialRate: ServiceSpecialRatesExt
|
|
119
|
+
): ServiceSpecialRatesExt {
|
|
120
|
+
if (specialRate.serviceRate) {
|
|
121
|
+
specialRate.serviceRate = generalRatesConvertRatesFromDB(
|
|
122
|
+
specialRate.serviceRate
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return specialRate;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function generalRatesConvertRatesToDB(
|
|
129
|
+
generalRate: ServiceRate
|
|
130
|
+
): ServiceRate {
|
|
131
|
+
return generalRatesConvertRatesToDBHelper(structuredClone(generalRate));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function generalRatesConvertRatesToDBHelper(
|
|
135
|
+
generalRate: ServiceRate
|
|
136
|
+
): ServiceRate {
|
|
137
|
+
// console.log(
|
|
138
|
+
// `generalRatesConvertRatesToDBHelper A: ${JSON.stringify(generalRate)}`
|
|
139
|
+
// );
|
|
140
|
+
if (generalRate?.dailyRate) {
|
|
141
|
+
generalRate.dailyRate = convertDollarsToCents(generalRate.dailyRate);
|
|
142
|
+
}
|
|
143
|
+
if (generalRate?.hourlyRate) {
|
|
144
|
+
generalRate.hourlyRate = convertDollarsToCents(generalRate.hourlyRate);
|
|
145
|
+
}
|
|
146
|
+
if (generalRate?.cleaningFeePerBooking) {
|
|
147
|
+
generalRate.cleaningFeePerBooking = convertDollarsToCents(
|
|
148
|
+
generalRate.cleaningFeePerBooking
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
// console.log(
|
|
152
|
+
// `generalRatesConvertRatesToDBHelper B: ${JSON.stringify(generalRate)}`
|
|
153
|
+
// );
|
|
154
|
+
return generalRate;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function generalRatesConvertRatesFromDB(
|
|
158
|
+
generalRate: ServiceRate
|
|
159
|
+
): ServiceRate {
|
|
160
|
+
// console.log(
|
|
161
|
+
// `generalRatesConvertRatesFromDB A: ${JSON.stringify(generalRate)}`
|
|
162
|
+
// );
|
|
163
|
+
// console.log(`specialRateConvertRatesFromDB`);
|
|
164
|
+
if (generalRate?.dailyRate) {
|
|
165
|
+
generalRate.dailyRate = convertCentsToDollars(generalRate.dailyRate);
|
|
166
|
+
}
|
|
167
|
+
if (generalRate?.hourlyRate) {
|
|
168
|
+
generalRate.hourlyRate = convertCentsToDollars(generalRate.hourlyRate);
|
|
169
|
+
}
|
|
170
|
+
if (generalRate?.cleaningFeePerBooking) {
|
|
171
|
+
generalRate.cleaningFeePerBooking = convertCentsToDollars(
|
|
172
|
+
generalRate.cleaningFeePerBooking
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
// console.log(
|
|
176
|
+
// `generalRatesConvertRatesFromDB B: ${JSON.stringify(generalRate)}`
|
|
177
|
+
// );
|
|
178
|
+
return generalRate;
|
|
179
|
+
}
|