@blackcode_sa/metaestetics-api 1.10.0 → 1.11.1
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/dist/admin/index.d.mts +337 -319
- package/dist/admin/index.d.ts +337 -319
- package/dist/admin/index.js +98 -79
- package/dist/admin/index.mjs +98 -79
- package/dist/backoffice/index.d.mts +284 -67
- package/dist/backoffice/index.d.ts +284 -67
- package/dist/backoffice/index.js +114 -6
- package/dist/backoffice/index.mjs +112 -6
- package/dist/index.d.mts +3145 -3065
- package/dist/index.d.ts +3145 -3065
- package/dist/index.js +460 -141
- package/dist/index.mjs +463 -143
- package/package.json +3 -1
- package/src/admin/booking/booking.admin.ts +2 -0
- package/src/admin/booking/booking.calculator.ts +121 -117
- package/src/admin/booking/booking.types.ts +3 -0
- package/src/backoffice/expo-safe/index.ts +2 -0
- package/src/backoffice/services/README.md +40 -0
- package/src/backoffice/services/constants.service.ts +268 -0
- package/src/backoffice/services/technology.service.ts +122 -10
- package/src/backoffice/types/admin-constants.types.ts +69 -0
- package/src/backoffice/types/index.ts +1 -0
- package/src/backoffice/types/product.types.ts +3 -1
- package/src/backoffice/types/technology.types.ts +4 -4
- package/src/backoffice/validations/schemas.ts +35 -9
- package/src/services/appointment/appointment.service.ts +0 -5
- package/src/services/appointment/utils/appointment.utils.ts +124 -113
- package/src/services/clinic/clinic.service.ts +163 -82
- package/src/services/procedure/procedure.service.ts +435 -234
- package/src/types/appointment/index.ts +9 -3
- package/src/types/clinic/index.ts +3 -6
- package/src/types/patient/medical-info.types.ts +3 -3
- package/src/types/procedure/index.ts +20 -17
- package/src/validations/appointment.schema.ts +2 -0
- package/src/validations/clinic.schema.ts +3 -6
- package/src/validations/patient/medical-info.schema.ts +7 -2
- package/src/validations/procedure.schema.ts +8 -10
- package/src/backoffice/services/__tests__/brand.service.test.ts +0 -196
- package/src/backoffice/services/__tests__/category.service.test.ts +0 -201
- package/src/backoffice/services/__tests__/product.service.test.ts +0 -358
- package/src/backoffice/services/__tests__/requirement.service.test.ts +0 -226
- package/src/backoffice/services/__tests__/subcategory.service.test.ts +0 -181
- package/src/backoffice/services/__tests__/technology.service.test.ts +0 -1097
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blackcode_sa/metaestetics-api",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.11.1",
|
|
5
5
|
"description": "Firebase authentication service with anonymous upgrade support",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@testing-library/jest-dom": "^6.6.3",
|
|
85
85
|
"@types/jest": "^29.5.14",
|
|
86
|
+
"@types/luxon": "^3.7.1",
|
|
86
87
|
"@types/mailgun-js": "^0.22.18",
|
|
87
88
|
"@types/node": "^20.17.13",
|
|
88
89
|
"@types/react": "^19.1.0",
|
|
@@ -98,6 +99,7 @@
|
|
|
98
99
|
"expo-server-sdk": "^3.13.0",
|
|
99
100
|
"firebase-admin": "^13.0.2",
|
|
100
101
|
"geofire-common": "^6.0.0",
|
|
102
|
+
"luxon": "^3.7.1",
|
|
101
103
|
"zod": "^3.24.1"
|
|
102
104
|
},
|
|
103
105
|
"optionalDependencies": {
|
|
@@ -233,6 +233,7 @@ export class BookingAdmin {
|
|
|
233
233
|
practitionerCalendarEvents: this.convertEventsTimestamps(
|
|
234
234
|
practitionerCalendarEvents
|
|
235
235
|
),
|
|
236
|
+
tz: clinic.location.tz || "UTC",
|
|
236
237
|
};
|
|
237
238
|
|
|
238
239
|
Logger.info("[BookingAdmin] Calling availability calculator", {
|
|
@@ -823,6 +824,7 @@ export class BookingAdmin {
|
|
|
823
824
|
calendarEventId: practitionerCalendarEventId,
|
|
824
825
|
clinicBranchId: procedure.clinicBranchId,
|
|
825
826
|
clinicInfo,
|
|
827
|
+
clinic_tz: clinicData.location.tz || "UTC",
|
|
826
828
|
practitionerId: procedure.practitionerId,
|
|
827
829
|
practitionerInfo,
|
|
828
830
|
patientId: data.patientId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Timestamp } from "firebase/firestore";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
2
3
|
import {
|
|
3
4
|
BookingAvailabilityRequest,
|
|
4
5
|
BookingAvailabilityResponse,
|
|
@@ -39,6 +40,7 @@ export class BookingAvailabilityCalculator {
|
|
|
39
40
|
timeframe,
|
|
40
41
|
clinicCalendarEvents,
|
|
41
42
|
practitionerCalendarEvents,
|
|
43
|
+
tz,
|
|
42
44
|
} = request;
|
|
43
45
|
|
|
44
46
|
// Get scheduling interval (default to 15 minutes if not specified)
|
|
@@ -61,7 +63,8 @@ export class BookingAvailabilityCalculator {
|
|
|
61
63
|
availableIntervals = this.applyClinicWorkingHours(
|
|
62
64
|
availableIntervals,
|
|
63
65
|
clinic.workingHours,
|
|
64
|
-
timeframe
|
|
66
|
+
timeframe,
|
|
67
|
+
tz
|
|
65
68
|
);
|
|
66
69
|
|
|
67
70
|
// Step 2: Subtract clinic blocking events
|
|
@@ -75,7 +78,8 @@ export class BookingAvailabilityCalculator {
|
|
|
75
78
|
availableIntervals,
|
|
76
79
|
practitioner,
|
|
77
80
|
clinic.id,
|
|
78
|
-
timeframe
|
|
81
|
+
timeframe,
|
|
82
|
+
tz
|
|
79
83
|
);
|
|
80
84
|
|
|
81
85
|
// Step 4: Subtract practitioner's busy times
|
|
@@ -104,12 +108,14 @@ export class BookingAvailabilityCalculator {
|
|
|
104
108
|
* @param intervals - Current available intervals
|
|
105
109
|
* @param workingHours - Clinic working hours
|
|
106
110
|
* @param timeframe - Overall timeframe being considered
|
|
111
|
+
* @param tz - IANA timezone of the clinic
|
|
107
112
|
* @returns Intervals filtered by clinic working hours
|
|
108
113
|
*/
|
|
109
114
|
private static applyClinicWorkingHours(
|
|
110
115
|
intervals: TimeInterval[],
|
|
111
116
|
workingHours: any, // Using 'any' for now since we're working with the existing type structure
|
|
112
|
-
timeframe: { start: Timestamp; end: Timestamp }
|
|
117
|
+
timeframe: { start: Timestamp; end: Timestamp },
|
|
118
|
+
tz: string
|
|
113
119
|
): TimeInterval[] {
|
|
114
120
|
if (!intervals.length) return [];
|
|
115
121
|
console.log(
|
|
@@ -120,7 +126,8 @@ export class BookingAvailabilityCalculator {
|
|
|
120
126
|
const workingIntervals = this.createWorkingHoursIntervals(
|
|
121
127
|
workingHours,
|
|
122
128
|
timeframe.start.toDate(),
|
|
123
|
-
timeframe.end.toDate()
|
|
129
|
+
timeframe.end.toDate(),
|
|
130
|
+
tz
|
|
124
131
|
);
|
|
125
132
|
|
|
126
133
|
// Intersect the available intervals with working hours intervals
|
|
@@ -133,44 +140,34 @@ export class BookingAvailabilityCalculator {
|
|
|
133
140
|
* @param workingHours - Working hours definition
|
|
134
141
|
* @param startDate - Start date of the overall timeframe
|
|
135
142
|
* @param endDate - End date of the overall timeframe
|
|
143
|
+
* @param tz - IANA timezone of the clinic
|
|
136
144
|
* @returns Array of time intervals representing working hours
|
|
137
145
|
*/
|
|
138
146
|
private static createWorkingHoursIntervals(
|
|
139
147
|
workingHours: any,
|
|
140
148
|
startDate: Date,
|
|
141
|
-
endDate: Date
|
|
149
|
+
endDate: Date,
|
|
150
|
+
tz: string
|
|
142
151
|
): TimeInterval[] {
|
|
143
152
|
const workingIntervals: TimeInterval[] = [];
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
friday: 5,
|
|
159
|
-
saturday: 6,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// Iterate through each day in the timeframe
|
|
163
|
-
while (currentDate <= endDate) {
|
|
164
|
-
const dayOfWeek = currentDate.getDay();
|
|
165
|
-
const dayName = Object.keys(dayNameToNumber).find(
|
|
166
|
-
(key) => dayNameToNumber[key] === dayOfWeek
|
|
167
|
-
);
|
|
153
|
+
let start = DateTime.fromJSDate(startDate, { zone: tz });
|
|
154
|
+
const end = DateTime.fromJSDate(endDate, { zone: tz });
|
|
155
|
+
|
|
156
|
+
while (start <= end) {
|
|
157
|
+
const dayOfWeek = start.weekday; // 1 for Monday, 7 for Sunday
|
|
158
|
+
const dayName = [
|
|
159
|
+
"monday",
|
|
160
|
+
"tuesday",
|
|
161
|
+
"wednesday",
|
|
162
|
+
"thursday",
|
|
163
|
+
"friday",
|
|
164
|
+
"saturday",
|
|
165
|
+
"sunday",
|
|
166
|
+
][dayOfWeek - 1];
|
|
168
167
|
|
|
169
168
|
if (dayName && workingHours[dayName]) {
|
|
170
169
|
const daySchedule = workingHours[dayName];
|
|
171
|
-
|
|
172
170
|
if (daySchedule) {
|
|
173
|
-
// Parse open and close times
|
|
174
171
|
const [openHours, openMinutes] = daySchedule.open
|
|
175
172
|
.split(":")
|
|
176
173
|
.map(Number);
|
|
@@ -178,25 +175,37 @@ export class BookingAvailabilityCalculator {
|
|
|
178
175
|
.split(":")
|
|
179
176
|
.map(Number);
|
|
180
177
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
178
|
+
let workStart = start.set({
|
|
179
|
+
hour: openHours,
|
|
180
|
+
minute: openMinutes,
|
|
181
|
+
second: 0,
|
|
182
|
+
millisecond: 0,
|
|
183
|
+
});
|
|
184
|
+
let workEnd = start.set({
|
|
185
|
+
hour: closeHours,
|
|
186
|
+
minute: closeMinutes,
|
|
187
|
+
second: 0,
|
|
188
|
+
millisecond: 0,
|
|
189
|
+
});
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
191
|
+
if (
|
|
192
|
+
workEnd.toMillis() > startDate.getTime() &&
|
|
193
|
+
workStart.toMillis() < endDate.getTime()
|
|
194
|
+
) {
|
|
195
|
+
const intervalStart =
|
|
196
|
+
workStart < DateTime.fromJSDate(startDate, { zone: tz })
|
|
197
|
+
? DateTime.fromJSDate(startDate, { zone: tz })
|
|
198
|
+
: workStart;
|
|
199
|
+
const intervalEnd =
|
|
200
|
+
workEnd > DateTime.fromJSDate(endDate, { zone: tz })
|
|
201
|
+
? DateTime.fromJSDate(endDate, { zone: tz })
|
|
202
|
+
: workEnd;
|
|
193
203
|
|
|
194
204
|
workingIntervals.push({
|
|
195
|
-
start: Timestamp.
|
|
196
|
-
end: Timestamp.
|
|
205
|
+
start: Timestamp.fromMillis(intervalStart.toMillis()),
|
|
206
|
+
end: Timestamp.fromMillis(intervalEnd.toMillis()),
|
|
197
207
|
});
|
|
198
208
|
|
|
199
|
-
// Handle breaks if they exist
|
|
200
209
|
if (daySchedule.breaks && daySchedule.breaks.length > 0) {
|
|
201
210
|
for (const breakTime of daySchedule.breaks) {
|
|
202
211
|
const [breakStartHours, breakStartMinutes] = breakTime.start
|
|
@@ -206,21 +215,23 @@ export class BookingAvailabilityCalculator {
|
|
|
206
215
|
.split(":")
|
|
207
216
|
.map(Number);
|
|
208
217
|
|
|
209
|
-
const breakStart =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
breakEnd.
|
|
218
|
+
const breakStart = start.set({
|
|
219
|
+
hour: breakStartHours,
|
|
220
|
+
minute: breakStartMinutes,
|
|
221
|
+
});
|
|
222
|
+
const breakEnd = start.set({
|
|
223
|
+
hour: breakEndHours,
|
|
224
|
+
minute: breakEndMinutes,
|
|
225
|
+
});
|
|
214
226
|
|
|
215
|
-
// Subtract this break from our working intervals
|
|
216
227
|
workingIntervals.splice(
|
|
217
228
|
-1,
|
|
218
229
|
1,
|
|
219
230
|
...this.subtractInterval(
|
|
220
231
|
workingIntervals[workingIntervals.length - 1],
|
|
221
232
|
{
|
|
222
|
-
start: Timestamp.
|
|
223
|
-
end: Timestamp.
|
|
233
|
+
start: Timestamp.fromMillis(breakStart.toMillis()),
|
|
234
|
+
end: Timestamp.fromMillis(breakEnd.toMillis()),
|
|
224
235
|
}
|
|
225
236
|
)
|
|
226
237
|
);
|
|
@@ -229,11 +240,8 @@ export class BookingAvailabilityCalculator {
|
|
|
229
240
|
}
|
|
230
241
|
}
|
|
231
242
|
}
|
|
232
|
-
|
|
233
|
-
// Move to the next day
|
|
234
|
-
currentDate.setDate(currentDate.getDate() + 1);
|
|
243
|
+
start = start.plus({ days: 1 });
|
|
235
244
|
}
|
|
236
|
-
|
|
237
245
|
return workingIntervals;
|
|
238
246
|
}
|
|
239
247
|
|
|
@@ -290,13 +298,15 @@ export class BookingAvailabilityCalculator {
|
|
|
290
298
|
* @param practitioner - Practitioner object
|
|
291
299
|
* @param clinicId - ID of the clinic
|
|
292
300
|
* @param timeframe - Overall timeframe being considered
|
|
301
|
+
* @param tz - IANA timezone of the clinic
|
|
293
302
|
* @returns Intervals filtered by practitioner's working hours
|
|
294
303
|
*/
|
|
295
304
|
private static applyPractitionerWorkingHours(
|
|
296
305
|
intervals: TimeInterval[],
|
|
297
306
|
practitioner: any,
|
|
298
307
|
clinicId: string,
|
|
299
|
-
timeframe: { start: Timestamp; end: Timestamp }
|
|
308
|
+
timeframe: { start: Timestamp; end: Timestamp },
|
|
309
|
+
tz: string
|
|
300
310
|
): TimeInterval[] {
|
|
301
311
|
if (!intervals.length) return [];
|
|
302
312
|
console.log(`Applying practitioner working hours for clinic ${clinicId}`);
|
|
@@ -319,7 +329,8 @@ export class BookingAvailabilityCalculator {
|
|
|
319
329
|
const workingIntervals = this.createPractitionerWorkingHoursIntervals(
|
|
320
330
|
clinicWorkingHours.workingHours,
|
|
321
331
|
timeframe.start.toDate(),
|
|
322
|
-
timeframe.end.toDate()
|
|
332
|
+
timeframe.end.toDate(),
|
|
333
|
+
tz
|
|
323
334
|
);
|
|
324
335
|
|
|
325
336
|
// Intersect the available intervals with practitioner's working hours intervals
|
|
@@ -332,74 +343,67 @@ export class BookingAvailabilityCalculator {
|
|
|
332
343
|
* @param workingHours - Practitioner's working hours definition
|
|
333
344
|
* @param startDate - Start date of the overall timeframe
|
|
334
345
|
* @param endDate - End date of the overall timeframe
|
|
346
|
+
* @param tz - IANA timezone of the clinic
|
|
335
347
|
* @returns Array of time intervals representing practitioner's working hours
|
|
336
348
|
*/
|
|
337
349
|
private static createPractitionerWorkingHoursIntervals(
|
|
338
350
|
workingHours: any,
|
|
339
351
|
startDate: Date,
|
|
340
|
-
endDate: Date
|
|
352
|
+
endDate: Date,
|
|
353
|
+
tz: string
|
|
341
354
|
): TimeInterval[] {
|
|
342
355
|
const workingIntervals: TimeInterval[] = [];
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
friday: 5,
|
|
358
|
-
saturday: 6,
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
// Iterate through each day in the timeframe
|
|
362
|
-
while (currentDate <= endDate) {
|
|
363
|
-
const dayOfWeek = currentDate.getDay();
|
|
364
|
-
const dayName = Object.keys(dayNameToNumber).find(
|
|
365
|
-
(key) => dayNameToNumber[key] === dayOfWeek
|
|
366
|
-
);
|
|
356
|
+
let start = DateTime.fromJSDate(startDate, { zone: tz });
|
|
357
|
+
const end = DateTime.fromJSDate(endDate, { zone: tz });
|
|
358
|
+
|
|
359
|
+
while (start <= end) {
|
|
360
|
+
const dayOfWeek = start.weekday;
|
|
361
|
+
const dayName = [
|
|
362
|
+
"monday",
|
|
363
|
+
"tuesday",
|
|
364
|
+
"wednesday",
|
|
365
|
+
"thursday",
|
|
366
|
+
"friday",
|
|
367
|
+
"saturday",
|
|
368
|
+
"sunday",
|
|
369
|
+
][dayOfWeek - 1];
|
|
367
370
|
|
|
368
371
|
if (dayName && workingHours[dayName]) {
|
|
369
372
|
const daySchedule = workingHours[dayName];
|
|
370
|
-
|
|
371
373
|
if (daySchedule) {
|
|
372
|
-
// Parse start and end times
|
|
373
374
|
const [startHours, startMinutes] = daySchedule.start
|
|
374
375
|
.split(":")
|
|
375
376
|
.map(Number);
|
|
376
377
|
const [endHours, endMinutes] = daySchedule.end.split(":").map(Number);
|
|
377
378
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const workEnd =
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const intervalStart =
|
|
389
|
-
|
|
379
|
+
const workStart = start.set({
|
|
380
|
+
hour: startHours,
|
|
381
|
+
minute: startMinutes,
|
|
382
|
+
});
|
|
383
|
+
const workEnd = start.set({ hour: endHours, minute: endMinutes });
|
|
384
|
+
|
|
385
|
+
if (
|
|
386
|
+
workEnd.toMillis() > startDate.getTime() &&
|
|
387
|
+
workStart.toMillis() < endDate.getTime()
|
|
388
|
+
) {
|
|
389
|
+
const intervalStart =
|
|
390
|
+
workStart < DateTime.fromJSDate(startDate, { zone: tz })
|
|
391
|
+
? DateTime.fromJSDate(startDate, { zone: tz })
|
|
392
|
+
: workStart;
|
|
393
|
+
const intervalEnd =
|
|
394
|
+
workEnd > DateTime.fromJSDate(endDate, { zone: tz })
|
|
395
|
+
? DateTime.fromJSDate(endDate, { zone: tz })
|
|
396
|
+
: workEnd;
|
|
390
397
|
|
|
391
398
|
workingIntervals.push({
|
|
392
|
-
start: Timestamp.
|
|
393
|
-
end: Timestamp.
|
|
399
|
+
start: Timestamp.fromMillis(intervalStart.toMillis()),
|
|
400
|
+
end: Timestamp.fromMillis(intervalEnd.toMillis()),
|
|
394
401
|
});
|
|
395
402
|
}
|
|
396
403
|
}
|
|
397
404
|
}
|
|
398
|
-
|
|
399
|
-
// Move to the next day
|
|
400
|
-
currentDate.setDate(currentDate.getDate() + 1);
|
|
405
|
+
start = start.plus({ days: 1 });
|
|
401
406
|
}
|
|
402
|
-
|
|
403
407
|
return workingIntervals;
|
|
404
408
|
}
|
|
405
409
|
|
|
@@ -485,32 +489,32 @@ export class BookingAvailabilityCalculator {
|
|
|
485
489
|
const intervalEnd = interval.end.toDate();
|
|
486
490
|
|
|
487
491
|
// Start at the beginning of the interval
|
|
488
|
-
let slotStart =
|
|
492
|
+
let slotStart = DateTime.fromJSDate(intervalStart);
|
|
489
493
|
|
|
490
494
|
// Adjust slotStart to the nearest interval boundary if needed
|
|
491
|
-
const minutesIntoDay = slotStart.
|
|
495
|
+
const minutesIntoDay = slotStart.hour * 60 + slotStart.minute;
|
|
492
496
|
const minutesRemainder = minutesIntoDay % intervalMinutes;
|
|
493
497
|
|
|
494
498
|
if (minutesRemainder > 0) {
|
|
495
|
-
slotStart.
|
|
496
|
-
|
|
497
|
-
);
|
|
499
|
+
slotStart = slotStart.plus({
|
|
500
|
+
minutes: intervalMinutes - minutesRemainder,
|
|
501
|
+
});
|
|
498
502
|
}
|
|
499
503
|
|
|
500
504
|
// Iterate through potential start times
|
|
501
|
-
while (slotStart.
|
|
505
|
+
while (slotStart.toMillis() + durationMs <= intervalEnd.getTime()) {
|
|
502
506
|
// Calculate potential end time
|
|
503
|
-
const slotEnd =
|
|
507
|
+
const slotEnd = slotStart.plus({ minutes: durationMinutes });
|
|
504
508
|
|
|
505
509
|
// Check if this slot fits entirely within one of our available intervals
|
|
506
510
|
if (this.isSlotFullyAvailable(slotStart, slotEnd, intervals)) {
|
|
507
511
|
slots.push({
|
|
508
|
-
start: Timestamp.
|
|
512
|
+
start: Timestamp.fromMillis(slotStart.toMillis()),
|
|
509
513
|
});
|
|
510
514
|
}
|
|
511
515
|
|
|
512
516
|
// Move to the next potential start time
|
|
513
|
-
slotStart =
|
|
517
|
+
slotStart = slotStart.plus({ minutes: intervalMinutes });
|
|
514
518
|
}
|
|
515
519
|
}
|
|
516
520
|
|
|
@@ -527,14 +531,14 @@ export class BookingAvailabilityCalculator {
|
|
|
527
531
|
* @returns True if the slot is fully contained within an available interval
|
|
528
532
|
*/
|
|
529
533
|
private static isSlotFullyAvailable(
|
|
530
|
-
slotStart:
|
|
531
|
-
slotEnd:
|
|
534
|
+
slotStart: DateTime,
|
|
535
|
+
slotEnd: DateTime,
|
|
532
536
|
intervals: TimeInterval[]
|
|
533
537
|
): boolean {
|
|
534
538
|
// Check if the slot is fully contained in any of the available intervals
|
|
535
539
|
return intervals.some((interval) => {
|
|
536
|
-
const intervalStart = interval.start.
|
|
537
|
-
const intervalEnd = interval.end.
|
|
540
|
+
const intervalStart = DateTime.fromMillis(interval.start.toMillis());
|
|
541
|
+
const intervalEnd = DateTime.fromMillis(interval.end.toMillis());
|
|
538
542
|
|
|
539
543
|
return slotStart >= intervalStart && slotEnd <= intervalEnd;
|
|
540
544
|
});
|
|
@@ -34,4 +34,6 @@ export {
|
|
|
34
34
|
export { Contraindication } from "../types/static/contraindication.types";
|
|
35
35
|
export { ProcedureFamily } from "../types/static/procedure-family.types";
|
|
36
36
|
export { TreatmentBenefit } from "../types/static/treatment-benefit.types";
|
|
37
|
+
export { TreatmentBenefitDynamic } from "../types/";
|
|
38
|
+
export { ContraindicationDynamic } from "../types/";
|
|
37
39
|
export { RequirementType, TimeUnit } from "../types/requirement.types";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Backoffice Services
|
|
2
|
+
|
|
3
|
+
This directory contains services used by the backoffice application.
|
|
4
|
+
|
|
5
|
+
## Services
|
|
6
|
+
|
|
7
|
+
### `CategoryService`
|
|
8
|
+
|
|
9
|
+
Manages procedure categories. Categories are the first level of organization after procedure family (aesthetics/surgery).
|
|
10
|
+
|
|
11
|
+
- **`create(category)`**: Creates a new category.
|
|
12
|
+
- **`getAll()`**: Retrieves all active categories.
|
|
13
|
+
- **`getAllByFamily(family)`**: Retrieves all active categories for a specific procedure family.
|
|
14
|
+
- **`update(id, category)`**: Updates an existing category.
|
|
15
|
+
- **`delete(id)`**: Soft deletes a category.
|
|
16
|
+
- **`getById(id)`**: Retrieves a category by its ID.
|
|
17
|
+
|
|
18
|
+
### `ProductService`
|
|
19
|
+
|
|
20
|
+
Manages products, which are sub-items of a `Technology`.
|
|
21
|
+
|
|
22
|
+
- **`create(technologyId, brandId, product)`**: Creates a new product under a technology.
|
|
23
|
+
- **`getAllByTechnology(technologyId)`**: Retrieves all products for a technology.
|
|
24
|
+
- **`getAllByBrand(brandId)`**: Retrieves all products for a brand.
|
|
25
|
+
- **`update(technologyId, productId, product)`**: Updates a product.
|
|
26
|
+
- **`delete(technologyId, productId)`**: Soft deletes a product.
|
|
27
|
+
- **`getById(technologyId, productId)`**: Retrieves a product by its ID.
|
|
28
|
+
|
|
29
|
+
### `ConstantsService`
|
|
30
|
+
|
|
31
|
+
Manages administrative constants like treatment benefits and contraindications.
|
|
32
|
+
|
|
33
|
+
- **`getTreatmentBenefits()`**: Retrieves all treatment benefits.
|
|
34
|
+
- **`addTreatmentBenefit(benefit)`**: Adds a new treatment benefit.
|
|
35
|
+
- **`updateTreatmentBenefit(benefit)`**: Updates an existing treatment benefit.
|
|
36
|
+
- **`deleteTreatmentBenefit(benefitId)`**: Deletes a treatment benefit.
|
|
37
|
+
- **`getContraindications()`**: Retrieves all contraindications.
|
|
38
|
+
- **`addContraindication(contraindication)`**: Adds a new contraindication.
|
|
39
|
+
- **`updateContraindication(contraindication)`**: Updates an existing contraindication.
|
|
40
|
+
- **`deleteContraindication(contraindicationId)`**: Deletes a contraindication.
|