@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.
Files changed (43) hide show
  1. package/dist/admin/index.d.mts +337 -319
  2. package/dist/admin/index.d.ts +337 -319
  3. package/dist/admin/index.js +98 -79
  4. package/dist/admin/index.mjs +98 -79
  5. package/dist/backoffice/index.d.mts +284 -67
  6. package/dist/backoffice/index.d.ts +284 -67
  7. package/dist/backoffice/index.js +114 -6
  8. package/dist/backoffice/index.mjs +112 -6
  9. package/dist/index.d.mts +3145 -3065
  10. package/dist/index.d.ts +3145 -3065
  11. package/dist/index.js +460 -141
  12. package/dist/index.mjs +463 -143
  13. package/package.json +3 -1
  14. package/src/admin/booking/booking.admin.ts +2 -0
  15. package/src/admin/booking/booking.calculator.ts +121 -117
  16. package/src/admin/booking/booking.types.ts +3 -0
  17. package/src/backoffice/expo-safe/index.ts +2 -0
  18. package/src/backoffice/services/README.md +40 -0
  19. package/src/backoffice/services/constants.service.ts +268 -0
  20. package/src/backoffice/services/technology.service.ts +122 -10
  21. package/src/backoffice/types/admin-constants.types.ts +69 -0
  22. package/src/backoffice/types/index.ts +1 -0
  23. package/src/backoffice/types/product.types.ts +3 -1
  24. package/src/backoffice/types/technology.types.ts +4 -4
  25. package/src/backoffice/validations/schemas.ts +35 -9
  26. package/src/services/appointment/appointment.service.ts +0 -5
  27. package/src/services/appointment/utils/appointment.utils.ts +124 -113
  28. package/src/services/clinic/clinic.service.ts +163 -82
  29. package/src/services/procedure/procedure.service.ts +435 -234
  30. package/src/types/appointment/index.ts +9 -3
  31. package/src/types/clinic/index.ts +3 -6
  32. package/src/types/patient/medical-info.types.ts +3 -3
  33. package/src/types/procedure/index.ts +20 -17
  34. package/src/validations/appointment.schema.ts +2 -0
  35. package/src/validations/clinic.schema.ts +3 -6
  36. package/src/validations/patient/medical-info.schema.ts +7 -2
  37. package/src/validations/procedure.schema.ts +8 -10
  38. package/src/backoffice/services/__tests__/brand.service.test.ts +0 -196
  39. package/src/backoffice/services/__tests__/category.service.test.ts +0 -201
  40. package/src/backoffice/services/__tests__/product.service.test.ts +0 -358
  41. package/src/backoffice/services/__tests__/requirement.service.test.ts +0 -226
  42. package/src/backoffice/services/__tests__/subcategory.service.test.ts +0 -181
  43. package/src/backoffice/services/__tests__/technology.service.test.ts +0 -1097
@@ -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
- const currentDate = new Date(startDate);
6189
- currentDate.setHours(0, 0, 0, 0);
6190
- const dayNameToNumber = {
6191
- sunday: 0,
6192
- monday: 1,
6193
- tuesday: 2,
6194
- wednesday: 3,
6195
- thursday: 4,
6196
- friday: 5,
6197
- saturday: 6
6198
- };
6199
- while (currentDate <= endDate) {
6200
- const dayOfWeek = currentDate.getDay();
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
- const workStart = new Date(currentDate);
6210
- workStart.setHours(openHours, openMinutes, 0, 0);
6211
- const workEnd = new Date(currentDate);
6212
- workEnd.setHours(closeHours, closeMinutes, 0, 0);
6213
- if (workEnd > startDate && workStart < endDate) {
6214
- const intervalStart = workStart < startDate ? startDate : workStart;
6215
- const intervalEnd = workEnd > endDate ? endDate : workEnd;
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.fromDate(intervalStart),
6218
- end: import_firestore2.Timestamp.fromDate(intervalEnd)
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 = new Date(currentDate);
6225
- breakStart.setHours(breakStartHours, breakStartMinutes, 0, 0);
6226
- const breakEnd = new Date(currentDate);
6227
- breakEnd.setHours(breakEndHours, breakEndMinutes, 0, 0);
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.fromDate(breakStart),
6235
- end: import_firestore2.Timestamp.fromDate(breakEnd)
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
- currentDate.setDate(currentDate.getDate() + 1);
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
- const currentDate = new Date(startDate);
6316
- currentDate.setHours(0, 0, 0, 0);
6317
- const dayNameToNumber = {
6318
- sunday: 0,
6319
- monday: 1,
6320
- tuesday: 2,
6321
- wednesday: 3,
6322
- thursday: 4,
6323
- friday: 5,
6324
- saturday: 6
6325
- };
6326
- while (currentDate <= endDate) {
6327
- const dayOfWeek = currentDate.getDay();
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 = new Date(currentDate);
6337
- workStart.setHours(startHours, startMinutes, 0, 0);
6338
- const workEnd = new Date(currentDate);
6339
- workEnd.setHours(endHours, endMinutes, 0, 0);
6340
- if (workEnd > startDate && workStart < endDate) {
6341
- const intervalStart = workStart < startDate ? startDate : workStart;
6342
- const intervalEnd = workEnd > endDate ? endDate : workEnd;
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.fromDate(intervalStart),
6345
- end: import_firestore2.Timestamp.fromDate(intervalEnd)
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
- currentDate.setDate(currentDate.getDate() + 1);
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 = new Date(intervalStart);
6406
- const minutesIntoDay = slotStart.getHours() * 60 + slotStart.getMinutes();
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.setMinutes(
6410
- slotStart.getMinutes() + (intervalMinutes - minutesRemainder)
6411
- );
6426
+ slotStart = slotStart.plus({
6427
+ minutes: intervalMinutes - minutesRemainder
6428
+ });
6412
6429
  }
6413
- while (slotStart.getTime() + durationMs <= intervalEnd.getTime()) {
6414
- const slotEnd = new Date(slotStart.getTime() + durationMs);
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.fromDate(slotStart)
6434
+ start: import_firestore2.Timestamp.fromMillis(slotStart.toMillis())
6418
6435
  });
6419
6436
  }
6420
- slotStart = new Date(slotStart.getTime() + intervalMs);
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.toDate();
6437
- const intervalEnd = interval.end.toDate();
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,
@@ -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
- const currentDate = new Date(startDate);
6129
- currentDate.setHours(0, 0, 0, 0);
6130
- const dayNameToNumber = {
6131
- sunday: 0,
6132
- monday: 1,
6133
- tuesday: 2,
6134
- wednesday: 3,
6135
- thursday: 4,
6136
- friday: 5,
6137
- saturday: 6
6138
- };
6139
- while (currentDate <= endDate) {
6140
- const dayOfWeek = currentDate.getDay();
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
- const workStart = new Date(currentDate);
6150
- workStart.setHours(openHours, openMinutes, 0, 0);
6151
- const workEnd = new Date(currentDate);
6152
- workEnd.setHours(closeHours, closeMinutes, 0, 0);
6153
- if (workEnd > startDate && workStart < endDate) {
6154
- const intervalStart = workStart < startDate ? startDate : workStart;
6155
- const intervalEnd = workEnd > endDate ? endDate : workEnd;
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.fromDate(intervalStart),
6158
- end: Timestamp.fromDate(intervalEnd)
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 = new Date(currentDate);
6165
- breakStart.setHours(breakStartHours, breakStartMinutes, 0, 0);
6166
- const breakEnd = new Date(currentDate);
6167
- breakEnd.setHours(breakEndHours, breakEndMinutes, 0, 0);
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.fromDate(breakStart),
6175
- end: Timestamp.fromDate(breakEnd)
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
- currentDate.setDate(currentDate.getDate() + 1);
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
- const currentDate = new Date(startDate);
6256
- currentDate.setHours(0, 0, 0, 0);
6257
- const dayNameToNumber = {
6258
- sunday: 0,
6259
- monday: 1,
6260
- tuesday: 2,
6261
- wednesday: 3,
6262
- thursday: 4,
6263
- friday: 5,
6264
- saturday: 6
6265
- };
6266
- while (currentDate <= endDate) {
6267
- const dayOfWeek = currentDate.getDay();
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 = new Date(currentDate);
6277
- workStart.setHours(startHours, startMinutes, 0, 0);
6278
- const workEnd = new Date(currentDate);
6279
- workEnd.setHours(endHours, endMinutes, 0, 0);
6280
- if (workEnd > startDate && workStart < endDate) {
6281
- const intervalStart = workStart < startDate ? startDate : workStart;
6282
- const intervalEnd = workEnd > endDate ? endDate : workEnd;
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.fromDate(intervalStart),
6285
- end: Timestamp.fromDate(intervalEnd)
6301
+ start: Timestamp.fromMillis(intervalStart.toMillis()),
6302
+ end: Timestamp.fromMillis(intervalEnd.toMillis())
6286
6303
  });
6287
6304
  }
6288
6305
  }
6289
6306
  }
6290
- currentDate.setDate(currentDate.getDate() + 1);
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 = new Date(intervalStart);
6346
- const minutesIntoDay = slotStart.getHours() * 60 + slotStart.getMinutes();
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.setMinutes(
6350
- slotStart.getMinutes() + (intervalMinutes - minutesRemainder)
6351
- );
6366
+ slotStart = slotStart.plus({
6367
+ minutes: intervalMinutes - minutesRemainder
6368
+ });
6352
6369
  }
6353
- while (slotStart.getTime() + durationMs <= intervalEnd.getTime()) {
6354
- const slotEnd = new Date(slotStart.getTime() + durationMs);
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.fromDate(slotStart)
6374
+ start: Timestamp.fromMillis(slotStart.toMillis())
6358
6375
  });
6359
6376
  }
6360
- slotStart = new Date(slotStart.getTime() + intervalMs);
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.toDate();
6377
- const intervalEnd = interval.end.toDate();
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,