@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.
@@ -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,
@@ -1093,6 +1093,7 @@ interface ClinicLocation {
1093
1093
  latitude: number;
1094
1094
  longitude: number;
1095
1095
  geohash?: string | null;
1096
+ tz?: string | null;
1096
1097
  }
1097
1098
 
1098
1099
  /**
@@ -1093,6 +1093,7 @@ interface ClinicLocation {
1093
1093
  latitude: number;
1094
1094
  longitude: number;
1095
1095
  geohash?: string | null;
1096
+ tz?: string | null;
1096
1097
  }
1097
1098
 
1098
1099
  /**
package/dist/index.d.mts CHANGED
@@ -977,6 +977,7 @@ interface ClinicLocation {
977
977
  latitude: number;
978
978
  longitude: number;
979
979
  geohash?: string | null;
980
+ tz?: string | null;
980
981
  }
981
982
  /**
982
983
  * Interface for working hours
@@ -1171,6 +1172,7 @@ interface CreateClinicGroupData {
1171
1172
  calendarSyncEnabled?: boolean;
1172
1173
  autoConfirmAppointments?: boolean;
1173
1174
  businessIdentificationNumber?: string | null;
1175
+ tz?: string | null;
1174
1176
  onboarding?: {
1175
1177
  completed?: boolean;
1176
1178
  step?: number;
@@ -2895,6 +2897,8 @@ interface Appointment {
2895
2897
  clinicBranchId: string;
2896
2898
  /** Aggregated clinic information (snapshot) */
2897
2899
  clinicInfo: ClinicInfo;
2900
+ /** IANA timezone of the clinic */
2901
+ clinic_tz: string;
2898
2902
  /** ID of the practitioner */
2899
2903
  practitionerId: string;
2900
2904
  /** Aggregated practitioner information (snapshot) */
@@ -2979,6 +2983,7 @@ interface CreateAppointmentData {
2979
2983
  patientNotes?: string | null;
2980
2984
  initialStatus: AppointmentStatus;
2981
2985
  initialPaymentStatus?: PaymentStatus;
2986
+ clinic_tz: string;
2982
2987
  }
2983
2988
  /**
2984
2989
  * Data needed to create a new Appointment via CreateAppointmentHttp method
@@ -3014,6 +3019,7 @@ interface UpdateAppointmentData {
3014
3019
  cost?: number;
3015
3020
  clinicBranchId?: string;
3016
3021
  practitionerId?: string;
3022
+ clinic_tz?: string;
3017
3023
  /** NEW: For updating linked forms - typically managed by dedicated methods */
3018
3024
  linkedFormIds?: string[] | FieldValue;
3019
3025
  linkedForms?: LinkedFormInfo[] | FieldValue;
@@ -3983,7 +3989,15 @@ declare class ClinicService extends BaseService {
3983
3989
  private clinicGroupService;
3984
3990
  private clinicAdminService;
3985
3991
  private mediaService;
3992
+ private functions;
3986
3993
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, clinicGroupService: ClinicGroupService, clinicAdminService: ClinicAdminService, mediaService: MediaService);
3994
+ /**
3995
+ * Get timezone from coordinates using the callable function
3996
+ * @param lat Latitude
3997
+ * @param lng Longitude
3998
+ * @returns IANA timezone string
3999
+ */
4000
+ private getTimezone;
3987
4001
  /**
3988
4002
  * Process media resource (string URL or File object)
3989
4003
  * @param media String URL or File object
package/dist/index.d.ts CHANGED
@@ -977,6 +977,7 @@ interface ClinicLocation {
977
977
  latitude: number;
978
978
  longitude: number;
979
979
  geohash?: string | null;
980
+ tz?: string | null;
980
981
  }
981
982
  /**
982
983
  * Interface for working hours
@@ -1171,6 +1172,7 @@ interface CreateClinicGroupData {
1171
1172
  calendarSyncEnabled?: boolean;
1172
1173
  autoConfirmAppointments?: boolean;
1173
1174
  businessIdentificationNumber?: string | null;
1175
+ tz?: string | null;
1174
1176
  onboarding?: {
1175
1177
  completed?: boolean;
1176
1178
  step?: number;
@@ -2895,6 +2897,8 @@ interface Appointment {
2895
2897
  clinicBranchId: string;
2896
2898
  /** Aggregated clinic information (snapshot) */
2897
2899
  clinicInfo: ClinicInfo;
2900
+ /** IANA timezone of the clinic */
2901
+ clinic_tz: string;
2898
2902
  /** ID of the practitioner */
2899
2903
  practitionerId: string;
2900
2904
  /** Aggregated practitioner information (snapshot) */
@@ -2979,6 +2983,7 @@ interface CreateAppointmentData {
2979
2983
  patientNotes?: string | null;
2980
2984
  initialStatus: AppointmentStatus;
2981
2985
  initialPaymentStatus?: PaymentStatus;
2986
+ clinic_tz: string;
2982
2987
  }
2983
2988
  /**
2984
2989
  * Data needed to create a new Appointment via CreateAppointmentHttp method
@@ -3014,6 +3019,7 @@ interface UpdateAppointmentData {
3014
3019
  cost?: number;
3015
3020
  clinicBranchId?: string;
3016
3021
  practitionerId?: string;
3022
+ clinic_tz?: string;
3017
3023
  /** NEW: For updating linked forms - typically managed by dedicated methods */
3018
3024
  linkedFormIds?: string[] | FieldValue;
3019
3025
  linkedForms?: LinkedFormInfo[] | FieldValue;
@@ -3983,7 +3989,15 @@ declare class ClinicService extends BaseService {
3983
3989
  private clinicGroupService;
3984
3990
  private clinicAdminService;
3985
3991
  private mediaService;
3992
+ private functions;
3986
3993
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, clinicGroupService: ClinicGroupService, clinicAdminService: ClinicAdminService, mediaService: MediaService);
3994
+ /**
3995
+ * Get timezone from coordinates using the callable function
3996
+ * @param lat Latitude
3997
+ * @param lng Longitude
3998
+ * @returns IANA timezone string
3999
+ */
4000
+ private getTimezone;
3987
4001
  /**
3988
4002
  * Process media resource (string URL or File object)
3989
4003
  * @param media String URL or File object