@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,350 @@
1
+ import { ServiceRate, ServiceRateType } from "@prisma/client";
2
+ import {
3
+ ServiceDailyRatesExt,
4
+ ServiceRateExt,
5
+ ServiceRatesAssociationExt,
6
+ ServiceSpecialRatesExt,
7
+ } from "../../extendedSchemas";
8
+ import {
9
+ LuxonDateRange,
10
+ dateTimeFromDate,
11
+ dateRangeWithinDateRange,
12
+ dateRangeDurationMatch,
13
+ dateTimeRangeToInterval,
14
+ dateRangeNormalizeToMWY,
15
+ dateTimeRangeIntersection,
16
+ dateTimeRangeHours,
17
+ } from "../luxonUtils";
18
+ import { DateTime } from "luxon";
19
+
20
+ export const SERVICE_DAILY_RATE_HOURS_MIN = 8;
21
+
22
+ export type ServiceRatePricingType = "Hourly" | "Daily";
23
+
24
+ export type ServiceDailyRatesExtLuxon = Exclude<
25
+ ServiceDailyRatesExt,
26
+ "startDate" | "endDate"
27
+ > & {
28
+ dateTimeRange: LuxonDateRange;
29
+ };
30
+
31
+ export type ServiceSpecialRatesExtLuxon = Exclude<
32
+ ServiceSpecialRatesExt,
33
+ "startDate" | "endDate"
34
+ > & {
35
+ dateTimeRange: LuxonDateRange;
36
+ };
37
+
38
+ export function serviceRateToLuxonRate(
39
+ rate: ServiceDailyRatesExt | ServiceSpecialRatesExt,
40
+ timezone: string | undefined | null
41
+ ): ServiceDailyRatesExtLuxon | ServiceSpecialRatesExtLuxon {
42
+ const { startDate, endDate, ...rest } = rate;
43
+
44
+ return {
45
+ ...rest,
46
+ dateTimeRange: {
47
+ start: dateTimeFromDate(startDate, undefined, timezone ?? undefined),
48
+ end: dateTimeFromDate(endDate, undefined, timezone ?? undefined),
49
+ },
50
+ } as ServiceDailyRatesExtLuxon | ServiceSpecialRatesExtLuxon;
51
+ }
52
+
53
+ export const ServiceRatePricingType = {
54
+ Hourly: "Hourly" as ServiceRatePricingType,
55
+ Daily: "Daily" as ServiceRatePricingType,
56
+ } as const;
57
+
58
+ export interface ServiceRatePricingInfo {
59
+ rateType: ServiceRateType;
60
+ pricingType: ServiceRatePricingType;
61
+
62
+ dateTimeRange: LuxonDateRange;
63
+
64
+ hours: number; //number of hours removed from total
65
+ unitCount: number; // number of days (8 hour blocks)/hours used unitCount * rate = piTotal
66
+ rate: number;
67
+ piTotal: number;
68
+
69
+ // cleaningFee: number; //only aplicable to generalRate,
70
+ // minimumTimeBlockHours: number; //only aplicable to generalRate
71
+ }
72
+
73
+ export function serviceRatePricingInfoGetTypeText(
74
+ pricingInfo: ServiceRatePricingInfo
75
+ ) {
76
+ return `${pricingInfo.rateType} ${pricingInfo.pricingType}`;
77
+ }
78
+
79
+ export function serviceRatesToPricingInfo(
80
+ dateTimeRange: LuxonDateRange,
81
+ serviceRates: (ServiceRate | null | undefined)[],
82
+ isFullDay: boolean,
83
+ hoursRemaining: number
84
+ ): ServiceRatePricingInfo {
85
+ let pricingInfo: ServiceRatePricingInfo = {
86
+ rateType: "General",
87
+ pricingType: "Hourly",
88
+ hours: 0,
89
+ piTotal: 0,
90
+ rate: 0,
91
+ unitCount: 0,
92
+ dateTimeRange: dateTimeRange,
93
+ // cleaningFee: 0,
94
+ // minimumTimeBlockHours: 0,
95
+ };
96
+
97
+ // Find the first serviceRate where dailyRate or hourlyRate is not null based on isFullDay
98
+ const validServiceRate = serviceRates.find(
99
+ (rate) =>
100
+ rate != null &&
101
+ ((isFullDay && rate.dailyRate != null) ||
102
+ (!isFullDay && rate.hourlyRate != null))
103
+ );
104
+
105
+ if (isFullDay && !validServiceRate) {
106
+ return serviceRatesToPricingInfo(
107
+ //default to non-daily rate if it is null
108
+ dateTimeRange,
109
+ serviceRates,
110
+ false,
111
+ hoursRemaining
112
+ );
113
+ }
114
+
115
+ if (!validServiceRate) {
116
+ return pricingInfo; // Return default if no valid serviceRate is found
117
+ }
118
+
119
+ // Set values based on the found serviceRate
120
+ // pricingInfo.cleaningFee = validServiceRate.cleaningFeePerBooking ?? 0;
121
+ // pricingInfo.minimumTimeBlockHours =
122
+ // validServiceRate.minimumTimeBlockHours ?? 0;
123
+ pricingInfo.rateType = validServiceRate.rateType ?? ServiceRateType.General;
124
+
125
+ if (isFullDay) {
126
+ pricingInfo.pricingType = ServiceRatePricingType.Daily;
127
+ const days = Math.floor(hoursRemaining / SERVICE_DAILY_RATE_HOURS_MIN);
128
+ pricingInfo.unitCount = days;
129
+ pricingInfo.hours = pricingInfo.unitCount * SERVICE_DAILY_RATE_HOURS_MIN;
130
+ pricingInfo.rate = validServiceRate.dailyRate ?? 0;
131
+ } else {
132
+ pricingInfo.pricingType = ServiceRatePricingType.Hourly;
133
+ pricingInfo.unitCount = hoursRemaining;
134
+ pricingInfo.hours = pricingInfo.unitCount;
135
+ pricingInfo.rate = validServiceRate.hourlyRate ?? 0;
136
+ }
137
+
138
+ pricingInfo.piTotal = pricingInfo.unitCount * pricingInfo.rate;
139
+
140
+ pricingInfo.dateTimeRange = {
141
+ start: dateTimeRange.start,
142
+ end: dateTimeRange.start.plus({ hours: pricingInfo.hours }),
143
+ };
144
+
145
+ return pricingInfo;
146
+ }
147
+
148
+ export function getServiceRatePricingInfo(
149
+ dateTimeRange: LuxonDateRange,
150
+ generalRate: ServiceRate | null | undefined,
151
+ dailyRates: ServiceDailyRatesExtLuxon[], //filtered to remove unavailable dates
152
+ specialRates: ServiceSpecialRatesExtLuxon[], //filtered to remove unavailable dates
153
+ // overlappingBusinessHours: LuxonDateRange[] | null,
154
+ timezone: string | null | undefined,
155
+ hoursRemaining: number
156
+ ): ServiceRatePricingInfo {
157
+ const specialRate = specialRates?.at(-1); //last element
158
+ const dailyRate = dailyRates?.at(-1); //last element
159
+ // const specialRate = findRateByDate(specialRates, dateTimeRange, timezone) as
160
+ // | ServiceSpecialRatesExt
161
+ // | undefined;
162
+ // const dailyRate = findRateByDate(dailyRates, dateTimeRange, timezone) as
163
+ // | ServiceDailyRatesExt
164
+ // | undefined;
165
+
166
+ // const isFullDay =
167
+ // dateTimeRange != null &&
168
+ // overlappingBusinessHours != null &&
169
+ // overlappingBusinessHours.length == 1
170
+ // ? dateRangeDurationMatch(dateTimeRange, overlappingBusinessHours[0])
171
+ // : false;
172
+
173
+ // const isFullDay =
174
+ // dateTimeRangeHours(dateTimeRange) >= SERVICE_DAILY_RATE_HOURS_MIN;
175
+ const isFullDay = hoursRemaining >= SERVICE_DAILY_RATE_HOURS_MIN;
176
+
177
+ //prefer specialRate over daily, and aily over general, etc...
178
+ const rates = [
179
+ specialRate?.serviceRate,
180
+ dailyRate?.serviceRate,
181
+ generalRate,
182
+ ].filter((rate) => !!rate);
183
+
184
+ return serviceRatesToPricingInfo(
185
+ dateTimeRange,
186
+ rates,
187
+ isFullDay,
188
+ hoursRemaining
189
+ );
190
+ }
191
+
192
+ export interface ServiceRatesLuxon {
193
+ generalRate: ServiceRate | null | undefined;
194
+ specialRates: ServiceSpecialRatesExtLuxon[];
195
+ dailyRates: ServiceDailyRatesExtLuxon[];
196
+ }
197
+
198
+ export function serviceGetFilteredRates(
199
+ serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
200
+ dateTimeRanges: LuxonDateRange[],
201
+ timezone: string | null | undefined
202
+ ): ServiceRatesLuxon {
203
+ return serviceRatesFilter(
204
+ serviceRatesToLuxonRates(serviceRatesAssociation, dateTimeRanges, timezone),
205
+ dateTimeRanges
206
+ );
207
+ }
208
+
209
+ export function serviceRatesFilter(
210
+ rates: ServiceRatesLuxon,
211
+ dateTimeRanges: LuxonDateRange[]
212
+ ): ServiceRatesLuxon {
213
+ const generalRate = rates.generalRate;
214
+
215
+ const intersectsWithAnyRange = (rateRange: LuxonDateRange) =>
216
+ dateTimeRanges.some(
217
+ (range) => dateTimeRangeIntersection(rateRange, range) != null
218
+ );
219
+
220
+ const specialRatesLuxon =
221
+ rates.specialRates
222
+ .filter(
223
+ (rate) => rate.isAvailable && intersectsWithAnyRange(rate.dateTimeRange)
224
+ )
225
+ .sort(
226
+ (a, b) =>
227
+ b.dateTimeRange.start.toMillis() - a.dateTimeRange.start.toMillis()
228
+ ) ?? [];
229
+
230
+ const dailyRatesLuxon =
231
+ rates.dailyRates
232
+ .filter((rate) => intersectsWithAnyRange(rate.dateTimeRange))
233
+ .sort(
234
+ (a, b) =>
235
+ b.dateTimeRange.start.toMillis() - a.dateTimeRange.start.toMillis()
236
+ ) ?? [];
237
+
238
+ return {
239
+ generalRate: generalRate,
240
+ specialRates: specialRatesLuxon,
241
+ dailyRates: dailyRatesLuxon,
242
+ };
243
+ }
244
+
245
+ export function serviceRatesHasRates(rates: ServiceRatesLuxon) {
246
+ const { generalRate, specialRates, dailyRates } = rates;
247
+
248
+ const isValidSpecialOrDailyRate = (
249
+ rate: ServiceSpecialRatesExt | ServiceDailyRatesExt
250
+ ) =>
251
+ rate?.serviceRate &&
252
+ ((rate.serviceRate?.dailyRate ?? 0) > 0 ||
253
+ (rate.serviceRate?.hourlyRate ?? 0) > 0);
254
+
255
+ const isValidGeneralRate = (rate: ServiceRate | undefined | null) =>
256
+ rate && ((rate.dailyRate ?? 0) > 0 || (rate.hourlyRate ?? 0) > 0);
257
+
258
+ const hasValidSpecialOrDailyRate = [...specialRates, ...dailyRates].some(
259
+ isValidSpecialOrDailyRate
260
+ );
261
+ const hasValidGeneralRate = isValidGeneralRate(generalRate);
262
+
263
+ const hasRates = hasValidSpecialOrDailyRate || hasValidGeneralRate;
264
+ return hasRates;
265
+ }
266
+
267
+ export function serviceRatesToLuxonRates(
268
+ serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
269
+ dateTimeRanges: LuxonDateRange[],
270
+ timezone: string | null | undefined
271
+ ): ServiceRatesLuxon {
272
+ const generalRate = serviceRatesAssociation?.serviceGeneralRates;
273
+
274
+ const specialRatesLuxon =
275
+ serviceRatesAssociation?.serviceSpecialRates
276
+ ?.filter((specialRate) => specialRate.isAvailable)
277
+ ?.map(
278
+ (rate) =>
279
+ serviceRateToLuxonRate(rate, timezone) as ServiceSpecialRatesExtLuxon
280
+ ) ?? [];
281
+
282
+ const dailyRatesLuxon =
283
+ serviceRatesAssociation?.serviceDailyRates?.map((rate) => {
284
+ const newRate = serviceRateToLuxonRate(
285
+ rate,
286
+ timezone
287
+ ) as ServiceDailyRatesExtLuxon;
288
+
289
+ dateTimeRanges.forEach((range) => {
290
+ newRate.dateTimeRange = dateRangeNormalizeToMWY(
291
+ newRate.dateTimeRange,
292
+ range
293
+ );
294
+ });
295
+
296
+ return newRate;
297
+ }) ?? [];
298
+
299
+ return {
300
+ generalRate: generalRate,
301
+ specialRates: specialRatesLuxon,
302
+ dailyRates: dailyRatesLuxon,
303
+ };
304
+ }
305
+
306
+ export function serviceRatesAssociationGetBlockedDates(
307
+ serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
308
+ timezone: string | undefined | null
309
+ ) {
310
+ return (
311
+ serviceRatesAssociation?.serviceSpecialRates
312
+ ?.filter((rate) => !rate.isAvailable)
313
+ .map((rate) => {
314
+ const rateRange = {
315
+ start: dateTimeFromDate(
316
+ rate.startDate,
317
+ undefined,
318
+ timezone ?? undefined
319
+ ),
320
+ end: dateTimeFromDate(rate.endDate, undefined, timezone ?? undefined),
321
+ } as LuxonDateRange;
322
+
323
+ return rateRange;
324
+ // return dateRangeNormalizeToMWY(rateRange, referenceRange);
325
+ }) ?? []
326
+ );
327
+ }
328
+
329
+ export function serviceRatesAssociationGetSpecialRates(
330
+ serviceRatesAssociation: ServiceRatesAssociationExt | null | undefined,
331
+ timezone: string | undefined | null
332
+ ) {
333
+ return (
334
+ serviceRatesAssociation?.serviceSpecialRates
335
+ ?.filter((rate) => rate.isAvailable)
336
+ .map((rate) => {
337
+ const rateRange = {
338
+ start: dateTimeFromDate(
339
+ rate.startDate,
340
+ undefined,
341
+ timezone ?? undefined
342
+ ),
343
+ end: dateTimeFromDate(rate.endDate, undefined, timezone ?? undefined),
344
+ } as LuxonDateRange;
345
+
346
+ return rateRange;
347
+ // return dateRangeNormalizeToMWY(rateRange, referenceRange);
348
+ }) ?? []
349
+ );
350
+ }
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ServiceCancelationPolicy as ServiceCancelationPolicyOption,
3
3
  ServiceSubscriptionStatus,
4
+ ServiceTypes,
4
5
  } from "@prisma/client";
6
+ import { ServiceExt } from "../../extendedSchemas";
5
7
 
6
8
  export type ServiceCancelationRefundPolicy = {
7
9
  days?: number;
@@ -148,3 +150,36 @@ export function serviceCheckoutUrl(
148
150
  ) {
149
151
  return `/checkout/services/${serviceId}/${serviceType}/${specificId}`;
150
152
  }
153
+
154
+ // Define the keys and values in a single object
155
+ const serviceKeysObject = {
156
+ eventService: true,
157
+ entertainmentService: true,
158
+ vendor: true,
159
+ exhibitor: true,
160
+ sponsor: true,
161
+ venue: true,
162
+ organization: true,
163
+ } as const;
164
+
165
+ export type ServiceSpecificName = keyof typeof serviceKeysObject;
166
+
167
+ export const serviceKeysArray = Object.keys(serviceKeysObject);
168
+
169
+ export type ServiceSpecificType = ServiceExt[ServiceSpecificName];
170
+
171
+ export const specificServiceMap: Record<ServiceTypes, ServiceSpecificName> = {
172
+ EventServices: "eventService",
173
+ EntertainmentServices: "entertainmentService",
174
+ Vendors: "vendor",
175
+ Exhibitors: "exhibitor",
176
+ Sponsors: "sponsor",
177
+ Venues: "venue",
178
+ Organizations: "organization",
179
+ } as const;
180
+
181
+ export const serviceTypeToField = (
182
+ serviceType: ServiceTypes
183
+ ): ServiceSpecificName => {
184
+ return specificServiceMap[serviceType];
185
+ };
@@ -1 +1,17 @@
1
1
  export type ValueOf<T> = T[keyof T];
2
+ export type UnionFromArray<T extends ReadonlyArray<any>> = T[number];
3
+ export type RemoveCommonProperties<T, U> = keyof (Omit<T, keyof U> &
4
+ Omit<U, keyof T>);
5
+
6
+ export type MakeOptional<T, K extends keyof T> = Omit<T, K> &
7
+ Partial<Pick<T, K>>;
8
+
9
+ // Create the final object dynamically
10
+ export function createAllTrueObject<T extends string>(
11
+ keys: T[]
12
+ ): Record<T, true> {
13
+ return keys.reduce((acc, key) => {
14
+ acc[key] = true;
15
+ return acc;
16
+ }, {} as Record<T, true>);
17
+ }
package/tsconfig.json CHANGED
@@ -12,8 +12,8 @@
12
12
  "forceConsistentCasingInFileNames": true,
13
13
  "jsx": "react-jsx",
14
14
  "noImplicitAny": true,
15
- "sourceMap": true,
15
+ "sourceMap": true
16
16
  },
17
- "include": ["src/**/*.ts", "src/**/*.tsx"],
17
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/types/**/*.d.ts"],
18
18
  "exclude": ["node_modules"]
19
19
  }