@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.
@@ -0,0 +1,3 @@
1
+ export function roundToNearestDivisor(num: number, div: number): number {
2
+ return Math.round(num * div) / div;
3
+ }
@@ -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
+ }