@blackcode_sa/metaestetics-api 1.10.0 → 1.11.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.
@@ -708,6 +708,7 @@ interface ClinicLocation {
708
708
  latitude: number;
709
709
  longitude: number;
710
710
  geohash?: string | null;
711
+ tz?: string | null;
711
712
  }
712
713
  /**
713
714
  * Interface for working hours
@@ -1582,6 +1583,8 @@ interface Appointment {
1582
1583
  clinicBranchId: string;
1583
1584
  /** Aggregated clinic information (snapshot) */
1584
1585
  clinicInfo: ClinicInfo;
1586
+ /** IANA timezone of the clinic */
1587
+ clinic_tz: string;
1585
1588
  /** ID of the practitioner */
1586
1589
  practitionerId: string;
1587
1590
  /** Aggregated practitioner information (snapshot) */
@@ -2923,6 +2926,8 @@ interface BookingAvailabilityRequest {
2923
2926
  clinicCalendarEvents: CalendarEvent[];
2924
2927
  /** Calendar events for the practitioner during the specified timeframe */
2925
2928
  practitionerCalendarEvents: CalendarEvent[];
2929
+ /** IANA timezone of the clinic */
2930
+ tz: string;
2926
2931
  }
2927
2932
  /**
2928
2933
  * Represents a single available booking slot
@@ -2968,6 +2973,7 @@ declare class BookingAvailabilityCalculator {
2968
2973
  * @param intervals - Current available intervals
2969
2974
  * @param workingHours - Clinic working hours
2970
2975
  * @param timeframe - Overall timeframe being considered
2976
+ * @param tz - IANA timezone of the clinic
2971
2977
  * @returns Intervals filtered by clinic working hours
2972
2978
  */
2973
2979
  private static applyClinicWorkingHours;
@@ -2977,6 +2983,7 @@ declare class BookingAvailabilityCalculator {
2977
2983
  * @param workingHours - Working hours definition
2978
2984
  * @param startDate - Start date of the overall timeframe
2979
2985
  * @param endDate - End date of the overall timeframe
2986
+ * @param tz - IANA timezone of the clinic
2980
2987
  * @returns Array of time intervals representing working hours
2981
2988
  */
2982
2989
  private static createWorkingHoursIntervals;
@@ -2995,6 +3002,7 @@ declare class BookingAvailabilityCalculator {
2995
3002
  * @param practitioner - Practitioner object
2996
3003
  * @param clinicId - ID of the clinic
2997
3004
  * @param timeframe - Overall timeframe being considered
3005
+ * @param tz - IANA timezone of the clinic
2998
3006
  * @returns Intervals filtered by practitioner's working hours
2999
3007
  */
3000
3008
  private static applyPractitionerWorkingHours;
@@ -3004,6 +3012,7 @@ declare class BookingAvailabilityCalculator {
3004
3012
  * @param workingHours - Practitioner's working hours definition
3005
3013
  * @param startDate - Start date of the overall timeframe
3006
3014
  * @param endDate - End date of the overall timeframe
3015
+ * @param tz - IANA timezone of the clinic
3007
3016
  * @returns Array of time intervals representing practitioner's working hours
3008
3017
  */
3009
3018
  private static createPractitionerWorkingHoursIntervals;
@@ -708,6 +708,7 @@ interface ClinicLocation {
708
708
  latitude: number;
709
709
  longitude: number;
710
710
  geohash?: string | null;
711
+ tz?: string | null;
711
712
  }
712
713
  /**
713
714
  * Interface for working hours
@@ -1582,6 +1583,8 @@ interface Appointment {
1582
1583
  clinicBranchId: string;
1583
1584
  /** Aggregated clinic information (snapshot) */
1584
1585
  clinicInfo: ClinicInfo;
1586
+ /** IANA timezone of the clinic */
1587
+ clinic_tz: string;
1585
1588
  /** ID of the practitioner */
1586
1589
  practitionerId: string;
1587
1590
  /** Aggregated practitioner information (snapshot) */
@@ -2923,6 +2926,8 @@ interface BookingAvailabilityRequest {
2923
2926
  clinicCalendarEvents: CalendarEvent[];
2924
2927
  /** Calendar events for the practitioner during the specified timeframe */
2925
2928
  practitionerCalendarEvents: CalendarEvent[];
2929
+ /** IANA timezone of the clinic */
2930
+ tz: string;
2926
2931
  }
2927
2932
  /**
2928
2933
  * Represents a single available booking slot
@@ -2968,6 +2973,7 @@ declare class BookingAvailabilityCalculator {
2968
2973
  * @param intervals - Current available intervals
2969
2974
  * @param workingHours - Clinic working hours
2970
2975
  * @param timeframe - Overall timeframe being considered
2976
+ * @param tz - IANA timezone of the clinic
2971
2977
  * @returns Intervals filtered by clinic working hours
2972
2978
  */
2973
2979
  private static applyClinicWorkingHours;
@@ -2977,6 +2983,7 @@ declare class BookingAvailabilityCalculator {
2977
2983
  * @param workingHours - Working hours definition
2978
2984
  * @param startDate - Start date of the overall timeframe
2979
2985
  * @param endDate - End date of the overall timeframe
2986
+ * @param tz - IANA timezone of the clinic
2980
2987
  * @returns Array of time intervals representing working hours
2981
2988
  */
2982
2989
  private static createWorkingHoursIntervals;
@@ -2995,6 +3002,7 @@ declare class BookingAvailabilityCalculator {
2995
3002
  * @param practitioner - Practitioner object
2996
3003
  * @param clinicId - ID of the clinic
2997
3004
  * @param timeframe - Overall timeframe being considered
3005
+ * @param tz - IANA timezone of the clinic
2998
3006
  * @returns Intervals filtered by practitioner's working hours
2999
3007
  */
3000
3008
  private static applyPractitionerWorkingHours;
@@ -3004,6 +3012,7 @@ declare class BookingAvailabilityCalculator {
3004
3012
  * @param workingHours - Practitioner's working hours definition
3005
3013
  * @param startDate - Start date of the overall timeframe
3006
3014
  * @param endDate - End date of the overall timeframe
3015
+ * @param tz - IANA timezone of the clinic
3007
3016
  * @returns Array of time intervals representing practitioner's working hours
3008
3017
  */
3009
3018
  private static createPractitionerWorkingHoursIntervals;
@@ -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,