@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/dist/admin/index.js
CHANGED
|
@@ -6102,6 +6102,7 @@ var ReviewsAggregationService = class {
|
|
|
6102
6102
|
|
|
6103
6103
|
// src/admin/booking/booking.calculator.ts
|
|
6104
6104
|
var import_firestore2 = require("firebase/firestore");
|
|
6105
|
+
var import_luxon = require("luxon");
|
|
6105
6106
|
var BookingAvailabilityCalculator = class {
|
|
6106
6107
|
/**
|
|
6107
6108
|
* Calculate available booking slots based on the provided data
|
|
@@ -6116,7 +6117,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6116
6117
|
procedure,
|
|
6117
6118
|
timeframe,
|
|
6118
6119
|
clinicCalendarEvents,
|
|
6119
|
-
practitionerCalendarEvents
|
|
6120
|
+
practitionerCalendarEvents,
|
|
6121
|
+
tz
|
|
6120
6122
|
} = request;
|
|
6121
6123
|
const schedulingIntervalMinutes = clinic.schedulingInterval || this.DEFAULT_INTERVAL_MINUTES;
|
|
6122
6124
|
const procedureDurationMinutes = procedure.duration;
|
|
@@ -6129,7 +6131,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6129
6131
|
availableIntervals = this.applyClinicWorkingHours(
|
|
6130
6132
|
availableIntervals,
|
|
6131
6133
|
clinic.workingHours,
|
|
6132
|
-
timeframe
|
|
6134
|
+
timeframe,
|
|
6135
|
+
tz
|
|
6133
6136
|
);
|
|
6134
6137
|
availableIntervals = this.subtractBlockingEvents(
|
|
6135
6138
|
availableIntervals,
|
|
@@ -6139,7 +6142,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6139
6142
|
availableIntervals,
|
|
6140
6143
|
practitioner,
|
|
6141
6144
|
clinic.id,
|
|
6142
|
-
timeframe
|
|
6145
|
+
timeframe,
|
|
6146
|
+
tz
|
|
6143
6147
|
);
|
|
6144
6148
|
availableIntervals = this.subtractPractitionerBusyTimes(
|
|
6145
6149
|
availableIntervals,
|
|
@@ -6161,9 +6165,10 @@ var BookingAvailabilityCalculator = class {
|
|
|
6161
6165
|
* @param intervals - Current available intervals
|
|
6162
6166
|
* @param workingHours - Clinic working hours
|
|
6163
6167
|
* @param timeframe - Overall timeframe being considered
|
|
6168
|
+
* @param tz - IANA timezone of the clinic
|
|
6164
6169
|
* @returns Intervals filtered by clinic working hours
|
|
6165
6170
|
*/
|
|
6166
|
-
static applyClinicWorkingHours(intervals, workingHours, timeframe) {
|
|
6171
|
+
static applyClinicWorkingHours(intervals, workingHours, timeframe, tz) {
|
|
6167
6172
|
if (!intervals.length) return [];
|
|
6168
6173
|
console.log(
|
|
6169
6174
|
`Applying clinic working hours to ${intervals.length} intervals`
|
|
@@ -6171,7 +6176,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6171
6176
|
const workingIntervals = this.createWorkingHoursIntervals(
|
|
6172
6177
|
workingHours,
|
|
6173
6178
|
timeframe.start.toDate(),
|
|
6174
|
-
timeframe.end.toDate()
|
|
6179
|
+
timeframe.end.toDate(),
|
|
6180
|
+
tz
|
|
6175
6181
|
);
|
|
6176
6182
|
return this.intersectIntervals(intervals, workingIntervals);
|
|
6177
6183
|
}
|
|
@@ -6181,58 +6187,68 @@ var BookingAvailabilityCalculator = class {
|
|
|
6181
6187
|
* @param workingHours - Working hours definition
|
|
6182
6188
|
* @param startDate - Start date of the overall timeframe
|
|
6183
6189
|
* @param endDate - End date of the overall timeframe
|
|
6190
|
+
* @param tz - IANA timezone of the clinic
|
|
6184
6191
|
* @returns Array of time intervals representing working hours
|
|
6185
6192
|
*/
|
|
6186
|
-
static createWorkingHoursIntervals(workingHours, startDate, endDate) {
|
|
6193
|
+
static createWorkingHoursIntervals(workingHours, startDate, endDate, tz) {
|
|
6187
6194
|
const workingIntervals = [];
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
const dayName = Object.keys(dayNameToNumber).find(
|
|
6202
|
-
(key) => dayNameToNumber[key] === dayOfWeek
|
|
6203
|
-
);
|
|
6195
|
+
let start = import_luxon.DateTime.fromJSDate(startDate, { zone: tz });
|
|
6196
|
+
const end = import_luxon.DateTime.fromJSDate(endDate, { zone: tz });
|
|
6197
|
+
while (start <= end) {
|
|
6198
|
+
const dayOfWeek = start.weekday;
|
|
6199
|
+
const dayName = [
|
|
6200
|
+
"monday",
|
|
6201
|
+
"tuesday",
|
|
6202
|
+
"wednesday",
|
|
6203
|
+
"thursday",
|
|
6204
|
+
"friday",
|
|
6205
|
+
"saturday",
|
|
6206
|
+
"sunday"
|
|
6207
|
+
][dayOfWeek - 1];
|
|
6204
6208
|
if (dayName && workingHours[dayName]) {
|
|
6205
6209
|
const daySchedule = workingHours[dayName];
|
|
6206
6210
|
if (daySchedule) {
|
|
6207
6211
|
const [openHours, openMinutes] = daySchedule.open.split(":").map(Number);
|
|
6208
6212
|
const [closeHours, closeMinutes] = daySchedule.close.split(":").map(Number);
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6213
|
+
let workStart = start.set({
|
|
6214
|
+
hour: openHours,
|
|
6215
|
+
minute: openMinutes,
|
|
6216
|
+
second: 0,
|
|
6217
|
+
millisecond: 0
|
|
6218
|
+
});
|
|
6219
|
+
let workEnd = start.set({
|
|
6220
|
+
hour: closeHours,
|
|
6221
|
+
minute: closeMinutes,
|
|
6222
|
+
second: 0,
|
|
6223
|
+
millisecond: 0
|
|
6224
|
+
});
|
|
6225
|
+
if (workEnd.toMillis() > startDate.getTime() && workStart.toMillis() < endDate.getTime()) {
|
|
6226
|
+
const intervalStart = workStart < import_luxon.DateTime.fromJSDate(startDate, { zone: tz }) ? import_luxon.DateTime.fromJSDate(startDate, { zone: tz }) : workStart;
|
|
6227
|
+
const intervalEnd = workEnd > import_luxon.DateTime.fromJSDate(endDate, { zone: tz }) ? import_luxon.DateTime.fromJSDate(endDate, { zone: tz }) : workEnd;
|
|
6216
6228
|
workingIntervals.push({
|
|
6217
|
-
start: import_firestore2.Timestamp.
|
|
6218
|
-
end: import_firestore2.Timestamp.
|
|
6229
|
+
start: import_firestore2.Timestamp.fromMillis(intervalStart.toMillis()),
|
|
6230
|
+
end: import_firestore2.Timestamp.fromMillis(intervalEnd.toMillis())
|
|
6219
6231
|
});
|
|
6220
6232
|
if (daySchedule.breaks && daySchedule.breaks.length > 0) {
|
|
6221
6233
|
for (const breakTime of daySchedule.breaks) {
|
|
6222
6234
|
const [breakStartHours, breakStartMinutes] = breakTime.start.split(":").map(Number);
|
|
6223
6235
|
const [breakEndHours, breakEndMinutes] = breakTime.end.split(":").map(Number);
|
|
6224
|
-
const breakStart =
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6236
|
+
const breakStart = start.set({
|
|
6237
|
+
hour: breakStartHours,
|
|
6238
|
+
minute: breakStartMinutes
|
|
6239
|
+
});
|
|
6240
|
+
const breakEnd = start.set({
|
|
6241
|
+
hour: breakEndHours,
|
|
6242
|
+
minute: breakEndMinutes
|
|
6243
|
+
});
|
|
6228
6244
|
workingIntervals.splice(
|
|
6229
6245
|
-1,
|
|
6230
6246
|
1,
|
|
6231
6247
|
...this.subtractInterval(
|
|
6232
6248
|
workingIntervals[workingIntervals.length - 1],
|
|
6233
6249
|
{
|
|
6234
|
-
start: import_firestore2.Timestamp.
|
|
6235
|
-
end: import_firestore2.Timestamp.
|
|
6250
|
+
start: import_firestore2.Timestamp.fromMillis(breakStart.toMillis()),
|
|
6251
|
+
end: import_firestore2.Timestamp.fromMillis(breakEnd.toMillis())
|
|
6236
6252
|
}
|
|
6237
6253
|
)
|
|
6238
6254
|
);
|
|
@@ -6241,7 +6257,7 @@ var BookingAvailabilityCalculator = class {
|
|
|
6241
6257
|
}
|
|
6242
6258
|
}
|
|
6243
6259
|
}
|
|
6244
|
-
|
|
6260
|
+
start = start.plus({ days: 1 });
|
|
6245
6261
|
}
|
|
6246
6262
|
return workingIntervals;
|
|
6247
6263
|
}
|
|
@@ -6281,9 +6297,10 @@ var BookingAvailabilityCalculator = class {
|
|
|
6281
6297
|
* @param practitioner - Practitioner object
|
|
6282
6298
|
* @param clinicId - ID of the clinic
|
|
6283
6299
|
* @param timeframe - Overall timeframe being considered
|
|
6300
|
+
* @param tz - IANA timezone of the clinic
|
|
6284
6301
|
* @returns Intervals filtered by practitioner's working hours
|
|
6285
6302
|
*/
|
|
6286
|
-
static applyPractitionerWorkingHours(intervals, practitioner, clinicId, timeframe) {
|
|
6303
|
+
static applyPractitionerWorkingHours(intervals, practitioner, clinicId, timeframe, tz) {
|
|
6287
6304
|
if (!intervals.length) return [];
|
|
6288
6305
|
console.log(`Applying practitioner working hours for clinic ${clinicId}`);
|
|
6289
6306
|
const clinicWorkingHours = practitioner.clinicWorkingHours.find(
|
|
@@ -6298,7 +6315,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6298
6315
|
const workingIntervals = this.createPractitionerWorkingHoursIntervals(
|
|
6299
6316
|
clinicWorkingHours.workingHours,
|
|
6300
6317
|
timeframe.start.toDate(),
|
|
6301
|
-
timeframe.end.toDate()
|
|
6318
|
+
timeframe.end.toDate(),
|
|
6319
|
+
tz
|
|
6302
6320
|
);
|
|
6303
6321
|
return this.intersectIntervals(intervals, workingIntervals);
|
|
6304
6322
|
}
|
|
@@ -6308,46 +6326,45 @@ var BookingAvailabilityCalculator = class {
|
|
|
6308
6326
|
* @param workingHours - Practitioner's working hours definition
|
|
6309
6327
|
* @param startDate - Start date of the overall timeframe
|
|
6310
6328
|
* @param endDate - End date of the overall timeframe
|
|
6329
|
+
* @param tz - IANA timezone of the clinic
|
|
6311
6330
|
* @returns Array of time intervals representing practitioner's working hours
|
|
6312
6331
|
*/
|
|
6313
|
-
static createPractitionerWorkingHoursIntervals(workingHours, startDate, endDate) {
|
|
6332
|
+
static createPractitionerWorkingHoursIntervals(workingHours, startDate, endDate, tz) {
|
|
6314
6333
|
const workingIntervals = [];
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
const dayName = Object.keys(dayNameToNumber).find(
|
|
6329
|
-
(key) => dayNameToNumber[key] === dayOfWeek
|
|
6330
|
-
);
|
|
6334
|
+
let start = import_luxon.DateTime.fromJSDate(startDate, { zone: tz });
|
|
6335
|
+
const end = import_luxon.DateTime.fromJSDate(endDate, { zone: tz });
|
|
6336
|
+
while (start <= end) {
|
|
6337
|
+
const dayOfWeek = start.weekday;
|
|
6338
|
+
const dayName = [
|
|
6339
|
+
"monday",
|
|
6340
|
+
"tuesday",
|
|
6341
|
+
"wednesday",
|
|
6342
|
+
"thursday",
|
|
6343
|
+
"friday",
|
|
6344
|
+
"saturday",
|
|
6345
|
+
"sunday"
|
|
6346
|
+
][dayOfWeek - 1];
|
|
6331
6347
|
if (dayName && workingHours[dayName]) {
|
|
6332
6348
|
const daySchedule = workingHours[dayName];
|
|
6333
6349
|
if (daySchedule) {
|
|
6334
6350
|
const [startHours, startMinutes] = daySchedule.start.split(":").map(Number);
|
|
6335
6351
|
const [endHours, endMinutes] = daySchedule.end.split(":").map(Number);
|
|
6336
|
-
const workStart =
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
const
|
|
6352
|
+
const workStart = start.set({
|
|
6353
|
+
hour: startHours,
|
|
6354
|
+
minute: startMinutes
|
|
6355
|
+
});
|
|
6356
|
+
const workEnd = start.set({ hour: endHours, minute: endMinutes });
|
|
6357
|
+
if (workEnd.toMillis() > startDate.getTime() && workStart.toMillis() < endDate.getTime()) {
|
|
6358
|
+
const intervalStart = workStart < import_luxon.DateTime.fromJSDate(startDate, { zone: tz }) ? import_luxon.DateTime.fromJSDate(startDate, { zone: tz }) : workStart;
|
|
6359
|
+
const intervalEnd = workEnd > import_luxon.DateTime.fromJSDate(endDate, { zone: tz }) ? import_luxon.DateTime.fromJSDate(endDate, { zone: tz }) : workEnd;
|
|
6343
6360
|
workingIntervals.push({
|
|
6344
|
-
start: import_firestore2.Timestamp.
|
|
6345
|
-
end: import_firestore2.Timestamp.
|
|
6361
|
+
start: import_firestore2.Timestamp.fromMillis(intervalStart.toMillis()),
|
|
6362
|
+
end: import_firestore2.Timestamp.fromMillis(intervalEnd.toMillis())
|
|
6346
6363
|
});
|
|
6347
6364
|
}
|
|
6348
6365
|
}
|
|
6349
6366
|
}
|
|
6350
|
-
|
|
6367
|
+
start = start.plus({ days: 1 });
|
|
6351
6368
|
}
|
|
6352
6369
|
return workingIntervals;
|
|
6353
6370
|
}
|
|
@@ -6402,22 +6419,22 @@ var BookingAvailabilityCalculator = class {
|
|
|
6402
6419
|
for (const interval of intervals) {
|
|
6403
6420
|
const intervalStart = interval.start.toDate();
|
|
6404
6421
|
const intervalEnd = interval.end.toDate();
|
|
6405
|
-
let slotStart =
|
|
6406
|
-
const minutesIntoDay = slotStart.
|
|
6422
|
+
let slotStart = import_luxon.DateTime.fromJSDate(intervalStart);
|
|
6423
|
+
const minutesIntoDay = slotStart.hour * 60 + slotStart.minute;
|
|
6407
6424
|
const minutesRemainder = minutesIntoDay % intervalMinutes;
|
|
6408
6425
|
if (minutesRemainder > 0) {
|
|
6409
|
-
slotStart.
|
|
6410
|
-
|
|
6411
|
-
);
|
|
6426
|
+
slotStart = slotStart.plus({
|
|
6427
|
+
minutes: intervalMinutes - minutesRemainder
|
|
6428
|
+
});
|
|
6412
6429
|
}
|
|
6413
|
-
while (slotStart.
|
|
6414
|
-
const slotEnd =
|
|
6430
|
+
while (slotStart.toMillis() + durationMs <= intervalEnd.getTime()) {
|
|
6431
|
+
const slotEnd = slotStart.plus({ minutes: durationMinutes });
|
|
6415
6432
|
if (this.isSlotFullyAvailable(slotStart, slotEnd, intervals)) {
|
|
6416
6433
|
slots.push({
|
|
6417
|
-
start: import_firestore2.Timestamp.
|
|
6434
|
+
start: import_firestore2.Timestamp.fromMillis(slotStart.toMillis())
|
|
6418
6435
|
});
|
|
6419
6436
|
}
|
|
6420
|
-
slotStart =
|
|
6437
|
+
slotStart = slotStart.plus({ minutes: intervalMinutes });
|
|
6421
6438
|
}
|
|
6422
6439
|
}
|
|
6423
6440
|
console.log(`Generated ${slots.length} available slots`);
|
|
@@ -6433,8 +6450,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6433
6450
|
*/
|
|
6434
6451
|
static isSlotFullyAvailable(slotStart, slotEnd, intervals) {
|
|
6435
6452
|
return intervals.some((interval) => {
|
|
6436
|
-
const intervalStart = interval.start.
|
|
6437
|
-
const intervalEnd = interval.end.
|
|
6453
|
+
const intervalStart = import_luxon.DateTime.fromMillis(interval.start.toMillis());
|
|
6454
|
+
const intervalEnd = import_luxon.DateTime.fromMillis(interval.end.toMillis());
|
|
6438
6455
|
return slotStart >= intervalStart && slotEnd <= intervalEnd;
|
|
6439
6456
|
});
|
|
6440
6457
|
}
|
|
@@ -6803,7 +6820,8 @@ var BookingAdmin = class {
|
|
|
6803
6820
|
clinicCalendarEvents: this.convertEventsTimestamps(clinicCalendarEvents),
|
|
6804
6821
|
practitionerCalendarEvents: this.convertEventsTimestamps(
|
|
6805
6822
|
practitionerCalendarEvents
|
|
6806
|
-
)
|
|
6823
|
+
),
|
|
6824
|
+
tz: clinic.location.tz || "UTC"
|
|
6807
6825
|
};
|
|
6808
6826
|
Logger.info("[BookingAdmin] Calling availability calculator", {
|
|
6809
6827
|
calculatorInputReady: true,
|
|
@@ -7184,6 +7202,7 @@ var BookingAdmin = class {
|
|
|
7184
7202
|
calendarEventId: practitionerCalendarEventId,
|
|
7185
7203
|
clinicBranchId: procedure.clinicBranchId,
|
|
7186
7204
|
clinicInfo,
|
|
7205
|
+
clinic_tz: clinicData.location.tz || "UTC",
|
|
7187
7206
|
practitionerId: procedure.practitionerId,
|
|
7188
7207
|
practitionerInfo,
|
|
7189
7208
|
patientId: data.patientId,
|
package/dist/admin/index.mjs
CHANGED
|
@@ -6042,6 +6042,7 @@ var ReviewsAggregationService = class {
|
|
|
6042
6042
|
|
|
6043
6043
|
// src/admin/booking/booking.calculator.ts
|
|
6044
6044
|
import { Timestamp } from "firebase/firestore";
|
|
6045
|
+
import { DateTime } from "luxon";
|
|
6045
6046
|
var BookingAvailabilityCalculator = class {
|
|
6046
6047
|
/**
|
|
6047
6048
|
* Calculate available booking slots based on the provided data
|
|
@@ -6056,7 +6057,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6056
6057
|
procedure,
|
|
6057
6058
|
timeframe,
|
|
6058
6059
|
clinicCalendarEvents,
|
|
6059
|
-
practitionerCalendarEvents
|
|
6060
|
+
practitionerCalendarEvents,
|
|
6061
|
+
tz
|
|
6060
6062
|
} = request;
|
|
6061
6063
|
const schedulingIntervalMinutes = clinic.schedulingInterval || this.DEFAULT_INTERVAL_MINUTES;
|
|
6062
6064
|
const procedureDurationMinutes = procedure.duration;
|
|
@@ -6069,7 +6071,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6069
6071
|
availableIntervals = this.applyClinicWorkingHours(
|
|
6070
6072
|
availableIntervals,
|
|
6071
6073
|
clinic.workingHours,
|
|
6072
|
-
timeframe
|
|
6074
|
+
timeframe,
|
|
6075
|
+
tz
|
|
6073
6076
|
);
|
|
6074
6077
|
availableIntervals = this.subtractBlockingEvents(
|
|
6075
6078
|
availableIntervals,
|
|
@@ -6079,7 +6082,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6079
6082
|
availableIntervals,
|
|
6080
6083
|
practitioner,
|
|
6081
6084
|
clinic.id,
|
|
6082
|
-
timeframe
|
|
6085
|
+
timeframe,
|
|
6086
|
+
tz
|
|
6083
6087
|
);
|
|
6084
6088
|
availableIntervals = this.subtractPractitionerBusyTimes(
|
|
6085
6089
|
availableIntervals,
|
|
@@ -6101,9 +6105,10 @@ var BookingAvailabilityCalculator = class {
|
|
|
6101
6105
|
* @param intervals - Current available intervals
|
|
6102
6106
|
* @param workingHours - Clinic working hours
|
|
6103
6107
|
* @param timeframe - Overall timeframe being considered
|
|
6108
|
+
* @param tz - IANA timezone of the clinic
|
|
6104
6109
|
* @returns Intervals filtered by clinic working hours
|
|
6105
6110
|
*/
|
|
6106
|
-
static applyClinicWorkingHours(intervals, workingHours, timeframe) {
|
|
6111
|
+
static applyClinicWorkingHours(intervals, workingHours, timeframe, tz) {
|
|
6107
6112
|
if (!intervals.length) return [];
|
|
6108
6113
|
console.log(
|
|
6109
6114
|
`Applying clinic working hours to ${intervals.length} intervals`
|
|
@@ -6111,7 +6116,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6111
6116
|
const workingIntervals = this.createWorkingHoursIntervals(
|
|
6112
6117
|
workingHours,
|
|
6113
6118
|
timeframe.start.toDate(),
|
|
6114
|
-
timeframe.end.toDate()
|
|
6119
|
+
timeframe.end.toDate(),
|
|
6120
|
+
tz
|
|
6115
6121
|
);
|
|
6116
6122
|
return this.intersectIntervals(intervals, workingIntervals);
|
|
6117
6123
|
}
|
|
@@ -6121,58 +6127,68 @@ var BookingAvailabilityCalculator = class {
|
|
|
6121
6127
|
* @param workingHours - Working hours definition
|
|
6122
6128
|
* @param startDate - Start date of the overall timeframe
|
|
6123
6129
|
* @param endDate - End date of the overall timeframe
|
|
6130
|
+
* @param tz - IANA timezone of the clinic
|
|
6124
6131
|
* @returns Array of time intervals representing working hours
|
|
6125
6132
|
*/
|
|
6126
|
-
static createWorkingHoursIntervals(workingHours, startDate, endDate) {
|
|
6133
|
+
static createWorkingHoursIntervals(workingHours, startDate, endDate, tz) {
|
|
6127
6134
|
const workingIntervals = [];
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
const dayName = Object.keys(dayNameToNumber).find(
|
|
6142
|
-
(key) => dayNameToNumber[key] === dayOfWeek
|
|
6143
|
-
);
|
|
6135
|
+
let start = DateTime.fromJSDate(startDate, { zone: tz });
|
|
6136
|
+
const end = DateTime.fromJSDate(endDate, { zone: tz });
|
|
6137
|
+
while (start <= end) {
|
|
6138
|
+
const dayOfWeek = start.weekday;
|
|
6139
|
+
const dayName = [
|
|
6140
|
+
"monday",
|
|
6141
|
+
"tuesday",
|
|
6142
|
+
"wednesday",
|
|
6143
|
+
"thursday",
|
|
6144
|
+
"friday",
|
|
6145
|
+
"saturday",
|
|
6146
|
+
"sunday"
|
|
6147
|
+
][dayOfWeek - 1];
|
|
6144
6148
|
if (dayName && workingHours[dayName]) {
|
|
6145
6149
|
const daySchedule = workingHours[dayName];
|
|
6146
6150
|
if (daySchedule) {
|
|
6147
6151
|
const [openHours, openMinutes] = daySchedule.open.split(":").map(Number);
|
|
6148
6152
|
const [closeHours, closeMinutes] = daySchedule.close.split(":").map(Number);
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6153
|
+
let workStart = start.set({
|
|
6154
|
+
hour: openHours,
|
|
6155
|
+
minute: openMinutes,
|
|
6156
|
+
second: 0,
|
|
6157
|
+
millisecond: 0
|
|
6158
|
+
});
|
|
6159
|
+
let workEnd = start.set({
|
|
6160
|
+
hour: closeHours,
|
|
6161
|
+
minute: closeMinutes,
|
|
6162
|
+
second: 0,
|
|
6163
|
+
millisecond: 0
|
|
6164
|
+
});
|
|
6165
|
+
if (workEnd.toMillis() > startDate.getTime() && workStart.toMillis() < endDate.getTime()) {
|
|
6166
|
+
const intervalStart = workStart < DateTime.fromJSDate(startDate, { zone: tz }) ? DateTime.fromJSDate(startDate, { zone: tz }) : workStart;
|
|
6167
|
+
const intervalEnd = workEnd > DateTime.fromJSDate(endDate, { zone: tz }) ? DateTime.fromJSDate(endDate, { zone: tz }) : workEnd;
|
|
6156
6168
|
workingIntervals.push({
|
|
6157
|
-
start: Timestamp.
|
|
6158
|
-
end: Timestamp.
|
|
6169
|
+
start: Timestamp.fromMillis(intervalStart.toMillis()),
|
|
6170
|
+
end: Timestamp.fromMillis(intervalEnd.toMillis())
|
|
6159
6171
|
});
|
|
6160
6172
|
if (daySchedule.breaks && daySchedule.breaks.length > 0) {
|
|
6161
6173
|
for (const breakTime of daySchedule.breaks) {
|
|
6162
6174
|
const [breakStartHours, breakStartMinutes] = breakTime.start.split(":").map(Number);
|
|
6163
6175
|
const [breakEndHours, breakEndMinutes] = breakTime.end.split(":").map(Number);
|
|
6164
|
-
const breakStart =
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6176
|
+
const breakStart = start.set({
|
|
6177
|
+
hour: breakStartHours,
|
|
6178
|
+
minute: breakStartMinutes
|
|
6179
|
+
});
|
|
6180
|
+
const breakEnd = start.set({
|
|
6181
|
+
hour: breakEndHours,
|
|
6182
|
+
minute: breakEndMinutes
|
|
6183
|
+
});
|
|
6168
6184
|
workingIntervals.splice(
|
|
6169
6185
|
-1,
|
|
6170
6186
|
1,
|
|
6171
6187
|
...this.subtractInterval(
|
|
6172
6188
|
workingIntervals[workingIntervals.length - 1],
|
|
6173
6189
|
{
|
|
6174
|
-
start: Timestamp.
|
|
6175
|
-
end: Timestamp.
|
|
6190
|
+
start: Timestamp.fromMillis(breakStart.toMillis()),
|
|
6191
|
+
end: Timestamp.fromMillis(breakEnd.toMillis())
|
|
6176
6192
|
}
|
|
6177
6193
|
)
|
|
6178
6194
|
);
|
|
@@ -6181,7 +6197,7 @@ var BookingAvailabilityCalculator = class {
|
|
|
6181
6197
|
}
|
|
6182
6198
|
}
|
|
6183
6199
|
}
|
|
6184
|
-
|
|
6200
|
+
start = start.plus({ days: 1 });
|
|
6185
6201
|
}
|
|
6186
6202
|
return workingIntervals;
|
|
6187
6203
|
}
|
|
@@ -6221,9 +6237,10 @@ var BookingAvailabilityCalculator = class {
|
|
|
6221
6237
|
* @param practitioner - Practitioner object
|
|
6222
6238
|
* @param clinicId - ID of the clinic
|
|
6223
6239
|
* @param timeframe - Overall timeframe being considered
|
|
6240
|
+
* @param tz - IANA timezone of the clinic
|
|
6224
6241
|
* @returns Intervals filtered by practitioner's working hours
|
|
6225
6242
|
*/
|
|
6226
|
-
static applyPractitionerWorkingHours(intervals, practitioner, clinicId, timeframe) {
|
|
6243
|
+
static applyPractitionerWorkingHours(intervals, practitioner, clinicId, timeframe, tz) {
|
|
6227
6244
|
if (!intervals.length) return [];
|
|
6228
6245
|
console.log(`Applying practitioner working hours for clinic ${clinicId}`);
|
|
6229
6246
|
const clinicWorkingHours = practitioner.clinicWorkingHours.find(
|
|
@@ -6238,7 +6255,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6238
6255
|
const workingIntervals = this.createPractitionerWorkingHoursIntervals(
|
|
6239
6256
|
clinicWorkingHours.workingHours,
|
|
6240
6257
|
timeframe.start.toDate(),
|
|
6241
|
-
timeframe.end.toDate()
|
|
6258
|
+
timeframe.end.toDate(),
|
|
6259
|
+
tz
|
|
6242
6260
|
);
|
|
6243
6261
|
return this.intersectIntervals(intervals, workingIntervals);
|
|
6244
6262
|
}
|
|
@@ -6248,46 +6266,45 @@ var BookingAvailabilityCalculator = class {
|
|
|
6248
6266
|
* @param workingHours - Practitioner's working hours definition
|
|
6249
6267
|
* @param startDate - Start date of the overall timeframe
|
|
6250
6268
|
* @param endDate - End date of the overall timeframe
|
|
6269
|
+
* @param tz - IANA timezone of the clinic
|
|
6251
6270
|
* @returns Array of time intervals representing practitioner's working hours
|
|
6252
6271
|
*/
|
|
6253
|
-
static createPractitionerWorkingHoursIntervals(workingHours, startDate, endDate) {
|
|
6272
|
+
static createPractitionerWorkingHoursIntervals(workingHours, startDate, endDate, tz) {
|
|
6254
6273
|
const workingIntervals = [];
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
const dayName = Object.keys(dayNameToNumber).find(
|
|
6269
|
-
(key) => dayNameToNumber[key] === dayOfWeek
|
|
6270
|
-
);
|
|
6274
|
+
let start = DateTime.fromJSDate(startDate, { zone: tz });
|
|
6275
|
+
const end = DateTime.fromJSDate(endDate, { zone: tz });
|
|
6276
|
+
while (start <= end) {
|
|
6277
|
+
const dayOfWeek = start.weekday;
|
|
6278
|
+
const dayName = [
|
|
6279
|
+
"monday",
|
|
6280
|
+
"tuesday",
|
|
6281
|
+
"wednesday",
|
|
6282
|
+
"thursday",
|
|
6283
|
+
"friday",
|
|
6284
|
+
"saturday",
|
|
6285
|
+
"sunday"
|
|
6286
|
+
][dayOfWeek - 1];
|
|
6271
6287
|
if (dayName && workingHours[dayName]) {
|
|
6272
6288
|
const daySchedule = workingHours[dayName];
|
|
6273
6289
|
if (daySchedule) {
|
|
6274
6290
|
const [startHours, startMinutes] = daySchedule.start.split(":").map(Number);
|
|
6275
6291
|
const [endHours, endMinutes] = daySchedule.end.split(":").map(Number);
|
|
6276
|
-
const workStart =
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
const
|
|
6292
|
+
const workStart = start.set({
|
|
6293
|
+
hour: startHours,
|
|
6294
|
+
minute: startMinutes
|
|
6295
|
+
});
|
|
6296
|
+
const workEnd = start.set({ hour: endHours, minute: endMinutes });
|
|
6297
|
+
if (workEnd.toMillis() > startDate.getTime() && workStart.toMillis() < endDate.getTime()) {
|
|
6298
|
+
const intervalStart = workStart < DateTime.fromJSDate(startDate, { zone: tz }) ? DateTime.fromJSDate(startDate, { zone: tz }) : workStart;
|
|
6299
|
+
const intervalEnd = workEnd > DateTime.fromJSDate(endDate, { zone: tz }) ? DateTime.fromJSDate(endDate, { zone: tz }) : workEnd;
|
|
6283
6300
|
workingIntervals.push({
|
|
6284
|
-
start: Timestamp.
|
|
6285
|
-
end: Timestamp.
|
|
6301
|
+
start: Timestamp.fromMillis(intervalStart.toMillis()),
|
|
6302
|
+
end: Timestamp.fromMillis(intervalEnd.toMillis())
|
|
6286
6303
|
});
|
|
6287
6304
|
}
|
|
6288
6305
|
}
|
|
6289
6306
|
}
|
|
6290
|
-
|
|
6307
|
+
start = start.plus({ days: 1 });
|
|
6291
6308
|
}
|
|
6292
6309
|
return workingIntervals;
|
|
6293
6310
|
}
|
|
@@ -6342,22 +6359,22 @@ var BookingAvailabilityCalculator = class {
|
|
|
6342
6359
|
for (const interval of intervals) {
|
|
6343
6360
|
const intervalStart = interval.start.toDate();
|
|
6344
6361
|
const intervalEnd = interval.end.toDate();
|
|
6345
|
-
let slotStart =
|
|
6346
|
-
const minutesIntoDay = slotStart.
|
|
6362
|
+
let slotStart = DateTime.fromJSDate(intervalStart);
|
|
6363
|
+
const minutesIntoDay = slotStart.hour * 60 + slotStart.minute;
|
|
6347
6364
|
const minutesRemainder = minutesIntoDay % intervalMinutes;
|
|
6348
6365
|
if (minutesRemainder > 0) {
|
|
6349
|
-
slotStart.
|
|
6350
|
-
|
|
6351
|
-
);
|
|
6366
|
+
slotStart = slotStart.plus({
|
|
6367
|
+
minutes: intervalMinutes - minutesRemainder
|
|
6368
|
+
});
|
|
6352
6369
|
}
|
|
6353
|
-
while (slotStart.
|
|
6354
|
-
const slotEnd =
|
|
6370
|
+
while (slotStart.toMillis() + durationMs <= intervalEnd.getTime()) {
|
|
6371
|
+
const slotEnd = slotStart.plus({ minutes: durationMinutes });
|
|
6355
6372
|
if (this.isSlotFullyAvailable(slotStart, slotEnd, intervals)) {
|
|
6356
6373
|
slots.push({
|
|
6357
|
-
start: Timestamp.
|
|
6374
|
+
start: Timestamp.fromMillis(slotStart.toMillis())
|
|
6358
6375
|
});
|
|
6359
6376
|
}
|
|
6360
|
-
slotStart =
|
|
6377
|
+
slotStart = slotStart.plus({ minutes: intervalMinutes });
|
|
6361
6378
|
}
|
|
6362
6379
|
}
|
|
6363
6380
|
console.log(`Generated ${slots.length} available slots`);
|
|
@@ -6373,8 +6390,8 @@ var BookingAvailabilityCalculator = class {
|
|
|
6373
6390
|
*/
|
|
6374
6391
|
static isSlotFullyAvailable(slotStart, slotEnd, intervals) {
|
|
6375
6392
|
return intervals.some((interval) => {
|
|
6376
|
-
const intervalStart = interval.start.
|
|
6377
|
-
const intervalEnd = interval.end.
|
|
6393
|
+
const intervalStart = DateTime.fromMillis(interval.start.toMillis());
|
|
6394
|
+
const intervalEnd = DateTime.fromMillis(interval.end.toMillis());
|
|
6378
6395
|
return slotStart >= intervalStart && slotEnd <= intervalEnd;
|
|
6379
6396
|
});
|
|
6380
6397
|
}
|
|
@@ -6743,7 +6760,8 @@ var BookingAdmin = class {
|
|
|
6743
6760
|
clinicCalendarEvents: this.convertEventsTimestamps(clinicCalendarEvents),
|
|
6744
6761
|
practitionerCalendarEvents: this.convertEventsTimestamps(
|
|
6745
6762
|
practitionerCalendarEvents
|
|
6746
|
-
)
|
|
6763
|
+
),
|
|
6764
|
+
tz: clinic.location.tz || "UTC"
|
|
6747
6765
|
};
|
|
6748
6766
|
Logger.info("[BookingAdmin] Calling availability calculator", {
|
|
6749
6767
|
calculatorInputReady: true,
|
|
@@ -7124,6 +7142,7 @@ var BookingAdmin = class {
|
|
|
7124
7142
|
calendarEventId: practitionerCalendarEventId,
|
|
7125
7143
|
clinicBranchId: procedure.clinicBranchId,
|
|
7126
7144
|
clinicInfo,
|
|
7145
|
+
clinic_tz: clinicData.location.tz || "UTC",
|
|
7127
7146
|
practitionerId: procedure.practitionerId,
|
|
7128
7147
|
practitionerInfo,
|
|
7129
7148
|
patientId: data.patientId,
|