@blackcode_sa/metaestetics-api 1.4.17 → 1.5.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.
Files changed (31) hide show
  1. package/dist/index.d.mts +9633 -7023
  2. package/dist/index.d.ts +9633 -7023
  3. package/dist/index.js +2773 -150
  4. package/dist/index.mjs +2809 -150
  5. package/package.json +4 -3
  6. package/src/index.ts +48 -1
  7. package/src/services/calendar/calendar-refactored.service.ts +1531 -0
  8. package/src/services/calendar/calendar.service.ts +1077 -0
  9. package/src/services/calendar/synced-calendars.service.ts +743 -0
  10. package/src/services/calendar/utils/appointment.utils.ts +314 -0
  11. package/src/services/calendar/utils/calendar-event.utils.ts +510 -0
  12. package/src/services/calendar/utils/clinic.utils.ts +237 -0
  13. package/src/services/calendar/utils/docs.utils.ts +157 -0
  14. package/src/services/calendar/utils/google-calendar.utils.ts +697 -0
  15. package/src/services/calendar/utils/index.ts +8 -0
  16. package/src/services/calendar/utils/patient.utils.ts +198 -0
  17. package/src/services/calendar/utils/practitioner.utils.ts +221 -0
  18. package/src/services/calendar/utils/synced-calendar.utils.ts +472 -0
  19. package/src/services/clinic/clinic.service.ts +2 -2
  20. package/src/services/clinic/utils/clinic.utils.ts +49 -47
  21. package/src/services/practitioner/practitioner.service.ts +1 -0
  22. package/src/types/calendar/index.ts +187 -0
  23. package/src/types/calendar/synced-calendar.types.ts +66 -0
  24. package/src/types/clinic/index.ts +4 -15
  25. package/src/types/index.ts +4 -0
  26. package/src/types/practitioner/index.ts +21 -0
  27. package/src/types/profile/index.ts +39 -0
  28. package/src/validations/calendar.schema.ts +223 -0
  29. package/src/validations/clinic.schema.ts +3 -3
  30. package/src/validations/practitioner.schema.ts +21 -0
  31. package/src/validations/profile-info.schema.ts +41 -0
package/dist/index.js CHANGED
@@ -25,9 +25,14 @@ __export(index_exports, {
25
25
  AllergyType: () => AllergyType,
26
26
  AuthService: () => AuthService,
27
27
  BlockingCondition: () => BlockingCondition,
28
+ CALENDAR_COLLECTION: () => CALENDAR_COLLECTION,
28
29
  CLINICS_COLLECTION: () => CLINICS_COLLECTION,
29
30
  CLINIC_ADMINS_COLLECTION: () => CLINIC_ADMINS_COLLECTION,
30
31
  CLINIC_GROUPS_COLLECTION: () => CLINIC_GROUPS_COLLECTION,
32
+ CalendarEventStatus: () => CalendarEventStatus,
33
+ CalendarEventType: () => CalendarEventType,
34
+ CalendarServiceV2: () => CalendarServiceV2,
35
+ CalendarSyncStatus: () => CalendarSyncStatus,
31
36
  CertificationLevel: () => CertificationLevel,
32
37
  CertificationSpecialty: () => CertificationSpecialty,
33
38
  ClinicAdminService: () => ClinicAdminService,
@@ -68,7 +73,10 @@ __export(index_exports, {
68
73
  PractitionerService: () => PractitionerService,
69
74
  PricingMeasure: () => PricingMeasure,
70
75
  ProcedureFamily: () => ProcedureFamily,
76
+ SYNCED_CALENDARS_COLLECTION: () => SYNCED_CALENDARS_COLLECTION,
71
77
  SubscriptionModel: () => SubscriptionModel,
78
+ SyncedCalendarProvider: () => SyncedCalendarProvider,
79
+ SyncedCalendarsService: () => SyncedCalendarsService,
72
80
  TreatmentBenefit: () => TreatmentBenefit,
73
81
  USER_ERRORS: () => USER_ERRORS,
74
82
  UserService: () => UserService,
@@ -85,6 +93,8 @@ __export(index_exports, {
85
93
  appointmentReminderNotificationSchema: () => appointmentReminderNotificationSchema,
86
94
  baseNotificationSchema: () => baseNotificationSchema,
87
95
  blockingConditionSchema: () => blockingConditionSchema,
96
+ calendarEventSchema: () => calendarEventSchema,
97
+ calendarEventTimeSchema: () => calendarEventTimeSchema,
88
98
  clinicAdminOptionsSchema: () => clinicAdminOptionsSchema,
89
99
  clinicAdminSchema: () => clinicAdminSchema,
90
100
  clinicAdminSignupSchema: () => clinicAdminSignupSchema,
@@ -100,6 +110,8 @@ __export(index_exports, {
100
110
  contactPersonSchema: () => contactPersonSchema,
101
111
  contraindicationSchema: () => contraindicationSchema,
102
112
  createAdminTokenSchema: () => createAdminTokenSchema,
113
+ createAppointmentSchema: () => createAppointmentSchema,
114
+ createCalendarEventSchema: () => createCalendarEventSchema,
103
115
  createClinicAdminSchema: () => createClinicAdminSchema,
104
116
  createClinicGroupSchema: () => createClinicGroupSchema,
105
117
  createClinicSchema: () => createClinicSchema,
@@ -131,21 +143,30 @@ __export(index_exports, {
131
143
  patientDoctorSchema: () => patientDoctorSchema,
132
144
  patientLocationInfoSchema: () => patientLocationInfoSchema,
133
145
  patientMedicalInfoSchema: () => patientMedicalInfoSchema,
146
+ patientProfileInfoSchema: () => patientProfileInfoSchema,
134
147
  patientProfileSchema: () => patientProfileSchema,
135
148
  patientSensitiveInfoSchema: () => patientSensitiveInfoSchema,
136
149
  postRequirementNotificationSchema: () => postRequirementNotificationSchema,
137
150
  practitionerBasicInfoSchema: () => practitionerBasicInfoSchema,
138
151
  practitionerCertificationSchema: () => practitionerCertificationSchema,
139
152
  practitionerClinicProceduresSchema: () => practitionerClinicProceduresSchema,
153
+ practitionerClinicWorkingHoursSchema: () => practitionerClinicWorkingHoursSchema,
154
+ practitionerProfileInfoSchema: () => practitionerProfileInfoSchema,
140
155
  practitionerReviewSchema: () => practitionerReviewSchema,
141
156
  practitionerSchema: () => practitionerSchema,
142
157
  practitionerWorkingHoursSchema: () => practitionerWorkingHoursSchema,
143
158
  preRequirementNotificationSchema: () => preRequirementNotificationSchema,
159
+ procedureCategorizationSchema: () => procedureCategorizationSchema,
160
+ procedureInfoSchema: () => procedureInfoSchema,
144
161
  reviewInfoSchema: () => reviewInfoSchema,
145
162
  serviceInfoSchema: () => serviceInfoSchema,
163
+ syncedCalendarEventSchema: () => syncedCalendarEventSchema,
164
+ timeSlotSchema: () => timeSlotSchema2,
146
165
  timestampSchema: () => timestampSchema,
147
166
  updateAllergySchema: () => updateAllergySchema,
167
+ updateAppointmentSchema: () => updateAppointmentSchema,
148
168
  updateBlockingConditionSchema: () => updateBlockingConditionSchema,
169
+ updateCalendarEventSchema: () => updateCalendarEventSchema,
149
170
  updateClinicAdminSchema: () => updateClinicAdminSchema,
150
171
  updateClinicGroupSchema: () => updateClinicGroupSchema,
151
172
  updateClinicSchema: () => updateClinicSchema,
@@ -253,6 +274,31 @@ var FilledDocumentStatus = /* @__PURE__ */ ((FilledDocumentStatus2) => {
253
274
  return FilledDocumentStatus2;
254
275
  })(FilledDocumentStatus || {});
255
276
 
277
+ // src/types/calendar/index.ts
278
+ var CalendarEventStatus = /* @__PURE__ */ ((CalendarEventStatus3) => {
279
+ CalendarEventStatus3["PENDING"] = "pending";
280
+ CalendarEventStatus3["CONFIRMED"] = "confirmed";
281
+ CalendarEventStatus3["REJECTED"] = "rejected";
282
+ CalendarEventStatus3["CANCELED"] = "canceled";
283
+ CalendarEventStatus3["RESCHEDULED"] = "rescheduled";
284
+ CalendarEventStatus3["COMPLETED"] = "completed";
285
+ return CalendarEventStatus3;
286
+ })(CalendarEventStatus || {});
287
+ var CalendarSyncStatus = /* @__PURE__ */ ((CalendarSyncStatus3) => {
288
+ CalendarSyncStatus3["INTERNAL"] = "internal";
289
+ CalendarSyncStatus3["EXTERNAL"] = "external";
290
+ return CalendarSyncStatus3;
291
+ })(CalendarSyncStatus || {});
292
+ var CalendarEventType = /* @__PURE__ */ ((CalendarEventType2) => {
293
+ CalendarEventType2["APPOINTMENT"] = "appointment";
294
+ CalendarEventType2["BLOCKING"] = "blocking";
295
+ CalendarEventType2["BREAK"] = "break";
296
+ CalendarEventType2["FREE_DAY"] = "free_day";
297
+ CalendarEventType2["OTHER"] = "other";
298
+ return CalendarEventType2;
299
+ })(CalendarEventType || {});
300
+ var CALENDAR_COLLECTION = "calendar";
301
+
256
302
  // src/types/index.ts
257
303
  var UserRole = /* @__PURE__ */ ((UserRole2) => {
258
304
  UserRole2["PATIENT"] = "patient";
@@ -1372,9 +1418,9 @@ var addAllergyUtil = async (db, patientId, data, userRef) => {
1372
1418
  var updateAllergyUtil = async (db, patientId, data, userRef) => {
1373
1419
  const validatedData = updateAllergySchema.parse(data);
1374
1420
  const { allergyIndex, ...updateData } = validatedData;
1375
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1376
- if (!doc14.exists()) throw new Error("Medical info not found");
1377
- const medicalInfo = doc14.data();
1421
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1422
+ if (!doc20.exists()) throw new Error("Medical info not found");
1423
+ const medicalInfo = doc20.data();
1378
1424
  if (allergyIndex >= medicalInfo.allergies.length) {
1379
1425
  throw new Error("Invalid allergy index");
1380
1426
  }
@@ -1390,9 +1436,9 @@ var updateAllergyUtil = async (db, patientId, data, userRef) => {
1390
1436
  });
1391
1437
  };
1392
1438
  var removeAllergyUtil = async (db, patientId, allergyIndex, userRef) => {
1393
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1394
- if (!doc14.exists()) throw new Error("Medical info not found");
1395
- const medicalInfo = doc14.data();
1439
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1440
+ if (!doc20.exists()) throw new Error("Medical info not found");
1441
+ const medicalInfo = doc20.data();
1396
1442
  if (allergyIndex >= medicalInfo.allergies.length) {
1397
1443
  throw new Error("Invalid allergy index");
1398
1444
  }
@@ -1417,9 +1463,9 @@ var addBlockingConditionUtil = async (db, patientId, data, userRef) => {
1417
1463
  var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
1418
1464
  const validatedData = updateBlockingConditionSchema.parse(data);
1419
1465
  const { conditionIndex, ...updateData } = validatedData;
1420
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1421
- if (!doc14.exists()) throw new Error("Medical info not found");
1422
- const medicalInfo = doc14.data();
1466
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1467
+ if (!doc20.exists()) throw new Error("Medical info not found");
1468
+ const medicalInfo = doc20.data();
1423
1469
  if (conditionIndex >= medicalInfo.blockingConditions.length) {
1424
1470
  throw new Error("Invalid blocking condition index");
1425
1471
  }
@@ -1435,9 +1481,9 @@ var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
1435
1481
  });
1436
1482
  };
1437
1483
  var removeBlockingConditionUtil = async (db, patientId, conditionIndex, userRef) => {
1438
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1439
- if (!doc14.exists()) throw new Error("Medical info not found");
1440
- const medicalInfo = doc14.data();
1484
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1485
+ if (!doc20.exists()) throw new Error("Medical info not found");
1486
+ const medicalInfo = doc20.data();
1441
1487
  if (conditionIndex >= medicalInfo.blockingConditions.length) {
1442
1488
  throw new Error("Invalid blocking condition index");
1443
1489
  }
@@ -1462,9 +1508,9 @@ var addContraindicationUtil = async (db, patientId, data, userRef) => {
1462
1508
  var updateContraindicationUtil = async (db, patientId, data, userRef) => {
1463
1509
  const validatedData = updateContraindicationSchema.parse(data);
1464
1510
  const { contraindicationIndex, ...updateData } = validatedData;
1465
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1466
- if (!doc14.exists()) throw new Error("Medical info not found");
1467
- const medicalInfo = doc14.data();
1511
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1512
+ if (!doc20.exists()) throw new Error("Medical info not found");
1513
+ const medicalInfo = doc20.data();
1468
1514
  if (contraindicationIndex >= medicalInfo.contraindications.length) {
1469
1515
  throw new Error("Invalid contraindication index");
1470
1516
  }
@@ -1480,9 +1526,9 @@ var updateContraindicationUtil = async (db, patientId, data, userRef) => {
1480
1526
  });
1481
1527
  };
1482
1528
  var removeContraindicationUtil = async (db, patientId, contraindicationIndex, userRef) => {
1483
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1484
- if (!doc14.exists()) throw new Error("Medical info not found");
1485
- const medicalInfo = doc14.data();
1529
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1530
+ if (!doc20.exists()) throw new Error("Medical info not found");
1531
+ const medicalInfo = doc20.data();
1486
1532
  if (contraindicationIndex >= medicalInfo.contraindications.length) {
1487
1533
  throw new Error("Invalid contraindication index");
1488
1534
  }
@@ -1507,9 +1553,9 @@ var addMedicationUtil = async (db, patientId, data, userRef) => {
1507
1553
  var updateMedicationUtil = async (db, patientId, data, userRef) => {
1508
1554
  const validatedData = updateMedicationSchema.parse(data);
1509
1555
  const { medicationIndex, ...updateData } = validatedData;
1510
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1511
- if (!doc14.exists()) throw new Error("Medical info not found");
1512
- const medicalInfo = doc14.data();
1556
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1557
+ if (!doc20.exists()) throw new Error("Medical info not found");
1558
+ const medicalInfo = doc20.data();
1513
1559
  if (medicationIndex >= medicalInfo.currentMedications.length) {
1514
1560
  throw new Error("Invalid medication index");
1515
1561
  }
@@ -1525,9 +1571,9 @@ var updateMedicationUtil = async (db, patientId, data, userRef) => {
1525
1571
  });
1526
1572
  };
1527
1573
  var removeMedicationUtil = async (db, patientId, medicationIndex, userRef) => {
1528
- const doc14 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1529
- if (!doc14.exists()) throw new Error("Medical info not found");
1530
- const medicalInfo = doc14.data();
1574
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1575
+ if (!doc20.exists()) throw new Error("Medical info not found");
1576
+ const medicalInfo = doc20.data();
1531
1577
  if (medicationIndex >= medicalInfo.currentMedications.length) {
1532
1578
  throw new Error("Invalid medication index");
1533
1579
  }
@@ -2320,14 +2366,14 @@ var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
2320
2366
  })(TreatmentBenefit || {});
2321
2367
 
2322
2368
  // src/backoffice/types/static/pricing.types.ts
2323
- var PricingMeasure = /* @__PURE__ */ ((PricingMeasure2) => {
2324
- PricingMeasure2["PER_ML"] = "per_ml";
2325
- PricingMeasure2["PER_ZONE"] = "per_zone";
2326
- PricingMeasure2["PER_AREA"] = "per_area";
2327
- PricingMeasure2["PER_SESSION"] = "per_session";
2328
- PricingMeasure2["PER_TREATMENT"] = "per_treatment";
2329
- PricingMeasure2["PER_PACKAGE"] = "per_package";
2330
- return PricingMeasure2;
2369
+ var PricingMeasure = /* @__PURE__ */ ((PricingMeasure3) => {
2370
+ PricingMeasure3["PER_ML"] = "per_ml";
2371
+ PricingMeasure3["PER_ZONE"] = "per_zone";
2372
+ PricingMeasure3["PER_AREA"] = "per_area";
2373
+ PricingMeasure3["PER_SESSION"] = "per_session";
2374
+ PricingMeasure3["PER_TREATMENT"] = "per_treatment";
2375
+ PricingMeasure3["PER_PACKAGE"] = "per_package";
2376
+ return PricingMeasure3;
2331
2377
  })(PricingMeasure || {});
2332
2378
  var Currency = /* @__PURE__ */ ((Currency2) => {
2333
2379
  Currency2["EUR"] = "EUR";
@@ -2507,7 +2553,7 @@ var clinicSchema = import_zod9.z.object({
2507
2553
  workingHours: workingHoursSchema,
2508
2554
  tags: import_zod9.z.array(import_zod9.z.nativeEnum(ClinicTag)),
2509
2555
  featuredPhotos: import_zod9.z.array(import_zod9.z.string()),
2510
- photos: import_zod9.z.array(import_zod9.z.string()),
2556
+ coverPhoto: import_zod9.z.string().nullable(),
2511
2557
  photosWithTags: import_zod9.z.array(
2512
2558
  import_zod9.z.object({
2513
2559
  url: import_zod9.z.string(),
@@ -2566,7 +2612,7 @@ var createClinicSchema = import_zod9.z.object({
2566
2612
  contactInfo: clinicContactInfoSchema,
2567
2613
  workingHours: workingHoursSchema,
2568
2614
  tags: import_zod9.z.array(import_zod9.z.nativeEnum(ClinicTag)),
2569
- photos: import_zod9.z.array(import_zod9.z.string()),
2615
+ coverPhoto: import_zod9.z.string().nullable(),
2570
2616
  photosWithTags: import_zod9.z.array(
2571
2617
  import_zod9.z.object({
2572
2618
  url: import_zod9.z.string(),
@@ -2627,7 +2673,7 @@ var clinicBranchSetupSchema = import_zod9.z.object({
2627
2673
  workingHours: workingHoursSchema,
2628
2674
  tags: import_zod9.z.array(import_zod9.z.nativeEnum(ClinicTag)),
2629
2675
  logo: import_zod9.z.string().optional(),
2630
- photos: import_zod9.z.array(import_zod9.z.string()),
2676
+ coverPhoto: import_zod9.z.string().nullable(),
2631
2677
  photosWithTags: import_zod9.z.array(
2632
2678
  import_zod9.z.object({
2633
2679
  url: import_zod9.z.string(),
@@ -2821,7 +2867,7 @@ async function getClinicAdminsByGroup(db, clinicGroupId) {
2821
2867
  (0, import_firestore11.where)("clinicGroupId", "==", clinicGroupId)
2822
2868
  );
2823
2869
  const querySnapshot = await (0, import_firestore11.getDocs)(q);
2824
- return querySnapshot.docs.map((doc14) => doc14.data());
2870
+ return querySnapshot.docs.map((doc20) => doc20.data());
2825
2871
  }
2826
2872
  async function updateClinicAdmin(db, adminId, data) {
2827
2873
  const admin = await getClinicAdmin(db, adminId);
@@ -3157,6 +3203,21 @@ var practitionerWorkingHoursSchema = import_zod10.z.object({
3157
3203
  createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3158
3204
  updatedAt: import_zod10.z.instanceof(import_firestore12.Timestamp)
3159
3205
  });
3206
+ var practitionerClinicWorkingHoursSchema = import_zod10.z.object({
3207
+ clinicId: import_zod10.z.string().min(1),
3208
+ workingHours: import_zod10.z.object({
3209
+ monday: timeSlotSchema,
3210
+ tuesday: timeSlotSchema,
3211
+ wednesday: timeSlotSchema,
3212
+ thursday: timeSlotSchema,
3213
+ friday: timeSlotSchema,
3214
+ saturday: timeSlotSchema,
3215
+ sunday: timeSlotSchema
3216
+ }),
3217
+ isActive: import_zod10.z.boolean(),
3218
+ createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3219
+ updatedAt: import_zod10.z.instanceof(import_firestore12.Timestamp)
3220
+ });
3160
3221
  var practitionerReviewSchema = import_zod10.z.object({
3161
3222
  id: import_zod10.z.string().min(1),
3162
3223
  practitionerId: import_zod10.z.string().min(1),
@@ -3182,6 +3243,7 @@ var practitionerSchema = import_zod10.z.object({
3182
3243
  basicInfo: practitionerBasicInfoSchema,
3183
3244
  certification: practitionerCertificationSchema,
3184
3245
  clinics: import_zod10.z.array(import_zod10.z.string()),
3246
+ clinicWorkingHours: import_zod10.z.array(practitionerClinicWorkingHoursSchema),
3185
3247
  isActive: import_zod10.z.boolean(),
3186
3248
  isVerified: import_zod10.z.boolean(),
3187
3249
  createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
@@ -3192,6 +3254,7 @@ var createPractitionerSchema = import_zod10.z.object({
3192
3254
  basicInfo: practitionerBasicInfoSchema,
3193
3255
  certification: practitionerCertificationSchema,
3194
3256
  clinics: import_zod10.z.array(import_zod10.z.string()).optional(),
3257
+ clinicWorkingHours: import_zod10.z.array(practitionerClinicWorkingHoursSchema).optional(),
3195
3258
  isActive: import_zod10.z.boolean(),
3196
3259
  isVerified: import_zod10.z.boolean()
3197
3260
  });
@@ -3241,6 +3304,7 @@ var PractitionerService = class extends BaseService {
3241
3304
  basicInfo: validatedData.basicInfo,
3242
3305
  certification: validatedData.certification,
3243
3306
  clinics: validatedData.clinics || [],
3307
+ clinicWorkingHours: validatedData.clinicWorkingHours || [],
3244
3308
  isActive: validatedData.isActive,
3245
3309
  isVerified: validatedData.isVerified,
3246
3310
  createdAt: (0, import_firestore13.serverTimestamp)(),
@@ -3303,7 +3367,7 @@ var PractitionerService = class extends BaseService {
3303
3367
  (0, import_firestore13.where)("isActive", "==", true)
3304
3368
  );
3305
3369
  const querySnapshot = await (0, import_firestore13.getDocs)(q);
3306
- return querySnapshot.docs.map((doc14) => doc14.data());
3370
+ return querySnapshot.docs.map((doc20) => doc20.data());
3307
3371
  }
3308
3372
  /**
3309
3373
  * Ažurira profil zdravstvenog radnika
@@ -3583,7 +3647,7 @@ var UserService = class extends BaseService {
3583
3647
  ];
3584
3648
  const q = (0, import_firestore14.query)((0, import_firestore14.collection)(this.db, USERS_COLLECTION), ...constraints);
3585
3649
  const querySnapshot = await (0, import_firestore14.getDocs)(q);
3586
- const users = querySnapshot.docs.map((doc14) => doc14.data());
3650
+ const users = querySnapshot.docs.map((doc20) => doc20.data());
3587
3651
  return Promise.all(users.map((userData) => userSchema.parse(userData)));
3588
3652
  }
3589
3653
  /**
@@ -3966,7 +4030,7 @@ async function getAllActiveGroups(db) {
3966
4030
  (0, import_firestore15.where)("isActive", "==", true)
3967
4031
  );
3968
4032
  const querySnapshot = await (0, import_firestore15.getDocs)(q);
3969
- return querySnapshot.docs.map((doc14) => doc14.data());
4033
+ return querySnapshot.docs.map((doc20) => doc20.data());
3970
4034
  }
3971
4035
  async function updateClinicGroup(db, groupId, data, app) {
3972
4036
  console.log("[CLINIC_GROUP] Updating clinic group", { groupId });
@@ -4423,25 +4487,23 @@ async function createClinic(db, data, creatorAdminId, clinicGroupService, clinic
4423
4487
  console.error("[CLINIC] Error processing logo:", logoError);
4424
4488
  }
4425
4489
  }
4426
- let processedPhotos = [];
4427
- if (validatedData.photos && validatedData.photos.length > 0) {
4428
- console.log("[CLINIC] Processing regular photos");
4490
+ let processedCoverPhoto = null;
4491
+ if (validatedData.coverPhoto) {
4492
+ console.log("[CLINIC] Processing cover photo");
4429
4493
  try {
4430
- processedPhotos = await uploadMultiplePhotos(
4431
- validatedData.photos,
4494
+ processedCoverPhoto = await uploadPhoto(
4495
+ validatedData.coverPhoto,
4432
4496
  "clinics",
4433
4497
  clinicId,
4434
- "photo",
4498
+ "cover",
4435
4499
  app
4436
4500
  );
4437
- console.log("[CLINIC] Regular photos processed", {
4438
- count: processedPhotos.length
4501
+ console.log("[CLINIC] Cover photo processed", {
4502
+ coverPhoto: processedCoverPhoto
4439
4503
  });
4440
- } catch (photosError) {
4441
- console.error("[CLINIC] Error processing regular photos:", photosError);
4442
- processedPhotos = validatedData.photos.filter(
4443
- (photo) => !photo.startsWith("data:")
4444
- );
4504
+ } catch (coverPhotoError) {
4505
+ console.error("[CLINIC] Error processing cover photo:", coverPhotoError);
4506
+ processedCoverPhoto = validatedData.coverPhoto.startsWith("data:") ? null : validatedData.coverPhoto;
4445
4507
  }
4446
4508
  }
4447
4509
  let processedFeaturedPhotos = [];
@@ -4527,7 +4589,7 @@ async function createClinic(db, data, creatorAdminId, clinicGroupService, clinic
4527
4589
  logo: logoUrl || "",
4528
4590
  tags: validatedData.tags || [],
4529
4591
  featuredPhotos: processedFeaturedPhotos || [],
4530
- photos: processedPhotos || [],
4592
+ coverPhoto: processedCoverPhoto,
4531
4593
  photosWithTags: processedPhotosWithTags,
4532
4594
  doctors: [],
4533
4595
  doctorsInfo: [],
@@ -4628,7 +4690,7 @@ async function getClinicsByGroup(db, groupId) {
4628
4690
  (0, import_firestore16.where)("isActive", "==", true)
4629
4691
  );
4630
4692
  const querySnapshot = await (0, import_firestore16.getDocs)(q);
4631
- return querySnapshot.docs.map((doc14) => doc14.data());
4693
+ return querySnapshot.docs.map((doc20) => doc20.data());
4632
4694
  }
4633
4695
  async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
4634
4696
  console.log("[CLINIC] Starting clinic update", { clinicId, adminId });
@@ -4682,36 +4744,32 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
4682
4744
  console.error("[CLINIC] Error processing logo update:", logoError);
4683
4745
  }
4684
4746
  }
4685
- if (data.photos && data.photos.length > 0) {
4686
- console.log("[CLINIC] Processing regular photos update");
4747
+ if (data.coverPhoto) {
4748
+ console.log("[CLINIC] Processing cover photo update");
4687
4749
  try {
4688
- const dataUrlPhotos = data.photos.filter(
4689
- (photo) => typeof photo === "string" && photo.startsWith("data:")
4690
- );
4691
- const existingPhotos = data.photos.filter(
4692
- (photo) => typeof photo === "string" && !photo.startsWith("data:")
4693
- );
4694
- if (dataUrlPhotos.length > 0) {
4695
- const uploadedPhotos = await uploadMultiplePhotos(
4696
- dataUrlPhotos,
4750
+ if (typeof data.coverPhoto === "string" && data.coverPhoto.startsWith("data:")) {
4751
+ const uploadedPhoto = await uploadPhoto(
4752
+ data.coverPhoto,
4697
4753
  "clinics",
4698
4754
  clinicId,
4699
- "photo",
4755
+ "cover",
4700
4756
  app
4701
4757
  );
4702
- console.log("[CLINIC] Regular photos update processed", {
4703
- count: uploadedPhotos.length
4704
- });
4705
- updatedData.photos = [...existingPhotos, ...uploadedPhotos];
4758
+ if (uploadedPhoto) {
4759
+ updatedData.coverPhoto = uploadedPhoto;
4760
+ }
4761
+ } else {
4762
+ updatedData.coverPhoto = data.coverPhoto;
4706
4763
  }
4707
- } catch (photosError) {
4764
+ console.log("[CLINIC] Cover photo update processed");
4765
+ } catch (photoError) {
4708
4766
  console.error(
4709
- "[CLINIC] Error processing regular photos update:",
4710
- photosError
4711
- );
4712
- updatedData.photos = data.photos.filter(
4713
- (photo) => typeof photo === "string" && !photo.startsWith("data:")
4767
+ "[CLINIC] Error processing cover photo update:",
4768
+ photoError
4714
4769
  );
4770
+ if (typeof data.coverPhoto === "string" && !data.coverPhoto.startsWith("data:")) {
4771
+ updatedData.coverPhoto = data.coverPhoto;
4772
+ }
4715
4773
  }
4716
4774
  }
4717
4775
  if (data.featuredPhotos && data.featuredPhotos.length > 0) {
@@ -4735,6 +4793,8 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
4735
4793
  count: uploadedPhotos.length
4736
4794
  });
4737
4795
  updatedData.featuredPhotos = [...existingPhotos, ...uploadedPhotos];
4796
+ } else {
4797
+ updatedData.featuredPhotos = existingPhotos;
4738
4798
  }
4739
4799
  } catch (featuredError) {
4740
4800
  console.error(
@@ -4842,7 +4902,7 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
4842
4902
  }
4843
4903
  const q = (0, import_firestore16.query)((0, import_firestore16.collection)(db, CLINICS_COLLECTION), ...constraints);
4844
4904
  const querySnapshot = await (0, import_firestore16.getDocs)(q);
4845
- return querySnapshot.docs.map((doc14) => doc14.data());
4905
+ return querySnapshot.docs.map((doc20) => doc20.data());
4846
4906
  }
4847
4907
  async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
4848
4908
  return getClinicsByAdmin(
@@ -4984,8 +5044,8 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
4984
5044
  }
4985
5045
  const q = (0, import_firestore18.query)((0, import_firestore18.collection)(db, CLINICS_COLLECTION), ...constraints);
4986
5046
  const querySnapshot = await (0, import_firestore18.getDocs)(q);
4987
- for (const doc14 of querySnapshot.docs) {
4988
- const clinic = doc14.data();
5047
+ for (const doc20 of querySnapshot.docs) {
5048
+ const clinic = doc20.data();
4989
5049
  const distance = (0, import_geofire_common4.distanceBetween)(
4990
5050
  [center.latitude, center.longitude],
4991
5051
  [clinic.location.latitude, clinic.location.longitude]
@@ -5163,7 +5223,7 @@ var ClinicService = class extends BaseService {
5163
5223
  contactInfo: setupData.contactInfo,
5164
5224
  workingHours: setupData.workingHours,
5165
5225
  tags: setupData.tags,
5166
- photos: setupData.photos || [],
5226
+ coverPhoto: setupData.coverPhoto || null,
5167
5227
  photosWithTags: setupData.photosWithTags || [],
5168
5228
  doctors: [],
5169
5229
  services: [],
@@ -5176,7 +5236,7 @@ var ClinicService = class extends BaseService {
5176
5236
  console.log("[CLINIC_SERVICE] Creating clinic branch with data", {
5177
5237
  name: createClinicData.name,
5178
5238
  hasLogo: !!createClinicData.logo,
5179
- photosCount: createClinicData.photos.length,
5239
+ hasCoverPhoto: !!createClinicData.coverPhoto,
5180
5240
  featuredPhotosCount: ((_a = createClinicData.featuredPhotos) == null ? void 0 : _a.length) || 0,
5181
5241
  photosWithTagsCount: ((_b = createClinicData.photosWithTags) == null ? void 0 : _b.length) || 0
5182
5242
  });
@@ -5846,9 +5906,9 @@ var NotificationService = class extends BaseService {
5846
5906
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5847
5907
  );
5848
5908
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5849
- return querySnapshot.docs.map((doc14) => ({
5850
- id: doc14.id,
5851
- ...doc14.data()
5909
+ return querySnapshot.docs.map((doc20) => ({
5910
+ id: doc20.id,
5911
+ ...doc20.data()
5852
5912
  }));
5853
5913
  }
5854
5914
  /**
@@ -5862,9 +5922,9 @@ var NotificationService = class extends BaseService {
5862
5922
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5863
5923
  );
5864
5924
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5865
- return querySnapshot.docs.map((doc14) => ({
5866
- id: doc14.id,
5867
- ...doc14.data()
5925
+ return querySnapshot.docs.map((doc20) => ({
5926
+ id: doc20.id,
5927
+ ...doc20.data()
5868
5928
  }));
5869
5929
  }
5870
5930
  /**
@@ -5936,9 +5996,9 @@ var NotificationService = class extends BaseService {
5936
5996
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5937
5997
  );
5938
5998
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5939
- return querySnapshot.docs.map((doc14) => ({
5940
- id: doc14.id,
5941
- ...doc14.data()
5999
+ return querySnapshot.docs.map((doc20) => ({
6000
+ id: doc20.id,
6001
+ ...doc20.data()
5942
6002
  }));
5943
6003
  }
5944
6004
  /**
@@ -5951,9 +6011,9 @@ var NotificationService = class extends BaseService {
5951
6011
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5952
6012
  );
5953
6013
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5954
- return querySnapshot.docs.map((doc14) => ({
5955
- id: doc14.id,
5956
- ...doc14.data()
6014
+ return querySnapshot.docs.map((doc20) => ({
6015
+ id: doc20.id,
6016
+ ...doc20.data()
5957
6017
  }));
5958
6018
  }
5959
6019
  };
@@ -6070,9 +6130,9 @@ var DocumentationTemplateService = class extends BaseService {
6070
6130
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6071
6131
  const templates = [];
6072
6132
  let lastVisible = null;
6073
- querySnapshot.forEach((doc14) => {
6074
- templates.push(doc14.data());
6075
- lastVisible = doc14;
6133
+ querySnapshot.forEach((doc20) => {
6134
+ templates.push(doc20.data());
6135
+ lastVisible = doc20;
6076
6136
  });
6077
6137
  return {
6078
6138
  templates,
@@ -6100,9 +6160,9 @@ var DocumentationTemplateService = class extends BaseService {
6100
6160
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6101
6161
  const templates = [];
6102
6162
  let lastVisible = null;
6103
- querySnapshot.forEach((doc14) => {
6104
- templates.push(doc14.data());
6105
- lastVisible = doc14;
6163
+ querySnapshot.forEach((doc20) => {
6164
+ templates.push(doc20.data());
6165
+ lastVisible = doc20;
6106
6166
  });
6107
6167
  return {
6108
6168
  templates,
@@ -6129,9 +6189,9 @@ var DocumentationTemplateService = class extends BaseService {
6129
6189
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6130
6190
  const templates = [];
6131
6191
  let lastVisible = null;
6132
- querySnapshot.forEach((doc14) => {
6133
- templates.push(doc14.data());
6134
- lastVisible = doc14;
6192
+ querySnapshot.forEach((doc20) => {
6193
+ templates.push(doc20.data());
6194
+ lastVisible = doc20;
6135
6195
  });
6136
6196
  return {
6137
6197
  templates,
@@ -6244,9 +6304,9 @@ var FilledDocumentService = class extends BaseService {
6244
6304
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6245
6305
  const documents = [];
6246
6306
  let lastVisible = null;
6247
- querySnapshot.forEach((doc14) => {
6248
- documents.push(doc14.data());
6249
- lastVisible = doc14;
6307
+ querySnapshot.forEach((doc20) => {
6308
+ documents.push(doc20.data());
6309
+ lastVisible = doc20;
6250
6310
  });
6251
6311
  return {
6252
6312
  documents,
@@ -6273,9 +6333,9 @@ var FilledDocumentService = class extends BaseService {
6273
6333
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6274
6334
  const documents = [];
6275
6335
  let lastVisible = null;
6276
- querySnapshot.forEach((doc14) => {
6277
- documents.push(doc14.data());
6278
- lastVisible = doc14;
6336
+ querySnapshot.forEach((doc20) => {
6337
+ documents.push(doc20.data());
6338
+ lastVisible = doc20;
6279
6339
  });
6280
6340
  return {
6281
6341
  documents,
@@ -6302,9 +6362,9 @@ var FilledDocumentService = class extends BaseService {
6302
6362
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6303
6363
  const documents = [];
6304
6364
  let lastVisible = null;
6305
- querySnapshot.forEach((doc14) => {
6306
- documents.push(doc14.data());
6307
- lastVisible = doc14;
6365
+ querySnapshot.forEach((doc20) => {
6366
+ documents.push(doc20.data());
6367
+ lastVisible = doc20;
6308
6368
  });
6309
6369
  return {
6310
6370
  documents,
@@ -6331,9 +6391,9 @@ var FilledDocumentService = class extends BaseService {
6331
6391
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6332
6392
  const documents = [];
6333
6393
  let lastVisible = null;
6334
- querySnapshot.forEach((doc14) => {
6335
- documents.push(doc14.data());
6336
- lastVisible = doc14;
6394
+ querySnapshot.forEach((doc20) => {
6395
+ documents.push(doc20.data());
6396
+ lastVisible = doc20;
6337
6397
  });
6338
6398
  return {
6339
6399
  documents,
@@ -6360,9 +6420,9 @@ var FilledDocumentService = class extends BaseService {
6360
6420
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6361
6421
  const documents = [];
6362
6422
  let lastVisible = null;
6363
- querySnapshot.forEach((doc14) => {
6364
- documents.push(doc14.data());
6365
- lastVisible = doc14;
6423
+ querySnapshot.forEach((doc20) => {
6424
+ documents.push(doc20.data());
6425
+ lastVisible = doc20;
6366
6426
  });
6367
6427
  return {
6368
6428
  documents,
@@ -6371,55 +6431,2597 @@ var FilledDocumentService = class extends BaseService {
6371
6431
  }
6372
6432
  };
6373
6433
 
6374
- // src/validations/notification.schema.ts
6434
+ // src/services/calendar/calendar-refactored.service.ts
6435
+ var import_firestore31 = require("firebase/firestore");
6436
+
6437
+ // src/types/calendar/synced-calendar.types.ts
6438
+ var SyncedCalendarProvider = /* @__PURE__ */ ((SyncedCalendarProvider3) => {
6439
+ SyncedCalendarProvider3["GOOGLE"] = "google";
6440
+ SyncedCalendarProvider3["OUTLOOK"] = "outlook";
6441
+ SyncedCalendarProvider3["APPLE"] = "apple";
6442
+ return SyncedCalendarProvider3;
6443
+ })(SyncedCalendarProvider || {});
6444
+ var SYNCED_CALENDARS_COLLECTION = "syncedCalendars";
6445
+
6446
+ // src/services/calendar/calendar-refactored.service.ts
6447
+ var import_firestore32 = require("firebase/firestore");
6448
+
6449
+ // src/validations/calendar.schema.ts
6450
+ var import_zod17 = require("zod");
6451
+ var import_firestore24 = require("firebase/firestore");
6452
+
6453
+ // src/validations/profile-info.schema.ts
6375
6454
  var import_zod16 = require("zod");
6376
- var baseNotificationSchema = import_zod16.z.object({
6377
- id: import_zod16.z.string().optional(),
6378
- userId: import_zod16.z.string(),
6379
- notificationTime: import_zod16.z.any(),
6455
+ var import_firestore23 = require("firebase/firestore");
6456
+ var clinicInfoSchema2 = import_zod16.z.object({
6457
+ id: import_zod16.z.string(),
6458
+ featuredPhoto: import_zod16.z.string(),
6459
+ name: import_zod16.z.string(),
6460
+ description: import_zod16.z.string(),
6461
+ location: clinicLocationSchema,
6462
+ contactInfo: clinicContactInfoSchema
6463
+ });
6464
+ var practitionerProfileInfoSchema = import_zod16.z.object({
6465
+ id: import_zod16.z.string(),
6466
+ practitionerPhoto: import_zod16.z.string().nullable(),
6467
+ name: import_zod16.z.string(),
6468
+ email: import_zod16.z.string().email(),
6469
+ phone: import_zod16.z.string().nullable(),
6470
+ certification: practitionerCertificationSchema
6471
+ });
6472
+ var patientProfileInfoSchema = import_zod16.z.object({
6473
+ id: import_zod16.z.string(),
6474
+ fullName: import_zod16.z.string(),
6475
+ email: import_zod16.z.string().email(),
6476
+ phone: import_zod16.z.string().nullable(),
6477
+ dateOfBirth: import_zod16.z.instanceof(import_firestore23.Timestamp),
6478
+ gender: import_zod16.z.nativeEnum(Gender)
6479
+ });
6480
+
6481
+ // src/validations/calendar.schema.ts
6482
+ var MIN_APPOINTMENT_DURATION = 15;
6483
+ var calendarEventTimeSchema = import_zod17.z.object({
6484
+ start: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp)),
6485
+ end: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp))
6486
+ }).refine(
6487
+ (data) => {
6488
+ const startDate = data.start instanceof import_firestore24.Timestamp ? data.start.toDate() : data.start;
6489
+ const endDate = data.end instanceof import_firestore24.Timestamp ? data.end.toDate() : data.end;
6490
+ return startDate < endDate;
6491
+ },
6492
+ {
6493
+ message: "End time must be after start time",
6494
+ path: ["end"]
6495
+ }
6496
+ ).refine(
6497
+ (data) => {
6498
+ const startDate = data.start instanceof import_firestore24.Timestamp ? data.start.toDate() : data.start;
6499
+ return startDate > /* @__PURE__ */ new Date();
6500
+ },
6501
+ {
6502
+ message: "Appointment must be scheduled in the future",
6503
+ path: ["start"]
6504
+ }
6505
+ );
6506
+ var timeSlotSchema2 = import_zod17.z.object({
6507
+ start: import_zod17.z.date(),
6508
+ end: import_zod17.z.date(),
6509
+ isAvailable: import_zod17.z.boolean()
6510
+ }).refine((data) => data.start < data.end, {
6511
+ message: "End time must be after start time",
6512
+ path: ["end"]
6513
+ });
6514
+ var syncedCalendarEventSchema = import_zod17.z.object({
6515
+ eventId: import_zod17.z.string(),
6516
+ syncedCalendarProvider: import_zod17.z.nativeEnum(SyncedCalendarProvider),
6517
+ syncedAt: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp))
6518
+ });
6519
+ var procedureInfoSchema = import_zod17.z.object({
6520
+ name: import_zod17.z.string(),
6521
+ description: import_zod17.z.string(),
6522
+ duration: import_zod17.z.number().min(MIN_APPOINTMENT_DURATION),
6523
+ price: import_zod17.z.number().min(0),
6524
+ currency: import_zod17.z.nativeEnum(Currency)
6525
+ });
6526
+ var procedureCategorizationSchema = import_zod17.z.object({
6527
+ procedureFamily: import_zod17.z.string(),
6528
+ // Replace with proper enum when available
6529
+ procedureCategory: import_zod17.z.string(),
6530
+ // Replace with proper enum when available
6531
+ procedureSubcategory: import_zod17.z.string(),
6532
+ // Replace with proper enum when available
6533
+ procedureTechnology: import_zod17.z.string(),
6534
+ // Replace with proper enum when available
6535
+ procedureProduct: import_zod17.z.string()
6536
+ // Replace with proper enum when available
6537
+ });
6538
+ var createAppointmentSchema = import_zod17.z.object({
6539
+ clinicId: import_zod17.z.string().min(1, "Clinic ID is required"),
6540
+ doctorId: import_zod17.z.string().min(1, "Doctor ID is required"),
6541
+ patientId: import_zod17.z.string().min(1, "Patient ID is required"),
6542
+ procedureId: import_zod17.z.string().min(1, "Procedure ID is required"),
6543
+ eventLocation: clinicLocationSchema,
6544
+ eventTime: calendarEventTimeSchema,
6545
+ description: import_zod17.z.string().optional()
6546
+ }).refine(
6547
+ (data) => {
6548
+ return true;
6549
+ },
6550
+ {
6551
+ message: "Invalid appointment parameters"
6552
+ }
6553
+ );
6554
+ var updateAppointmentSchema = import_zod17.z.object({
6555
+ appointmentId: import_zod17.z.string().min(1, "Appointment ID is required"),
6556
+ clinicId: import_zod17.z.string().min(1, "Clinic ID is required"),
6557
+ doctorId: import_zod17.z.string().min(1, "Doctor ID is required"),
6558
+ patientId: import_zod17.z.string().min(1, "Patient ID is required"),
6559
+ eventTime: calendarEventTimeSchema.optional(),
6560
+ description: import_zod17.z.string().optional(),
6561
+ status: import_zod17.z.nativeEnum(CalendarEventStatus).optional()
6562
+ });
6563
+ var createCalendarEventSchema = import_zod17.z.object({
6564
+ id: import_zod17.z.string(),
6565
+ clinicBranchId: import_zod17.z.string().nullable().optional(),
6566
+ clinicBranchInfo: import_zod17.z.any().nullable().optional(),
6567
+ practitionerProfileId: import_zod17.z.string().nullable().optional(),
6568
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
6569
+ patientProfileId: import_zod17.z.string().nullable().optional(),
6570
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
6571
+ procedureId: import_zod17.z.string().nullable().optional(),
6572
+ appointmentId: import_zod17.z.string().nullable().optional(),
6573
+ syncedCalendarEventId: import_zod17.z.array(syncedCalendarEventSchema).nullable().optional(),
6574
+ eventName: import_zod17.z.string().min(1, "Event name is required"),
6575
+ eventLocation: clinicLocationSchema.optional(),
6576
+ eventTime: calendarEventTimeSchema,
6577
+ description: import_zod17.z.string().optional(),
6578
+ status: import_zod17.z.nativeEnum(CalendarEventStatus),
6579
+ syncStatus: import_zod17.z.nativeEnum(CalendarSyncStatus),
6580
+ eventType: import_zod17.z.nativeEnum(CalendarEventType),
6581
+ createdAt: import_zod17.z.any(),
6582
+ // FieldValue for server timestamp
6583
+ updatedAt: import_zod17.z.any()
6584
+ // FieldValue for server timestamp
6585
+ });
6586
+ var updateCalendarEventSchema = import_zod17.z.object({
6587
+ syncedCalendarEventId: import_zod17.z.array(syncedCalendarEventSchema).nullable().optional(),
6588
+ appointmentId: import_zod17.z.string().nullable().optional(),
6589
+ eventName: import_zod17.z.string().optional(),
6590
+ eventTime: calendarEventTimeSchema.optional(),
6591
+ description: import_zod17.z.string().optional(),
6592
+ status: import_zod17.z.nativeEnum(CalendarEventStatus).optional(),
6593
+ syncStatus: import_zod17.z.nativeEnum(CalendarSyncStatus).optional(),
6594
+ eventType: import_zod17.z.nativeEnum(CalendarEventType).optional(),
6595
+ updatedAt: import_zod17.z.any()
6596
+ // FieldValue for server timestamp
6597
+ });
6598
+ var calendarEventSchema = import_zod17.z.object({
6599
+ id: import_zod17.z.string(),
6600
+ clinicBranchId: import_zod17.z.string().nullable().optional(),
6601
+ clinicBranchInfo: import_zod17.z.any().nullable().optional(),
6602
+ // Will be replaced with proper clinic info schema
6603
+ practitionerProfileId: import_zod17.z.string().nullable().optional(),
6604
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
6605
+ patientProfileId: import_zod17.z.string().nullable().optional(),
6606
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
6607
+ procedureId: import_zod17.z.string().nullable().optional(),
6608
+ procedureInfo: procedureInfoSchema.nullable().optional(),
6609
+ procedureCategorization: procedureCategorizationSchema.nullable().optional(),
6610
+ appointmentId: import_zod17.z.string().nullable().optional(),
6611
+ syncedCalendarEventId: import_zod17.z.array(syncedCalendarEventSchema).nullable().optional(),
6612
+ eventName: import_zod17.z.string(),
6613
+ eventLocation: clinicLocationSchema.optional(),
6614
+ eventTime: calendarEventTimeSchema,
6615
+ description: import_zod17.z.string().optional(),
6616
+ status: import_zod17.z.nativeEnum(CalendarEventStatus),
6617
+ syncStatus: import_zod17.z.nativeEnum(CalendarSyncStatus),
6618
+ eventType: import_zod17.z.nativeEnum(CalendarEventType),
6619
+ createdAt: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp)),
6620
+ updatedAt: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp))
6621
+ });
6622
+
6623
+ // src/services/calendar/utils/clinic.utils.ts
6624
+ var import_firestore26 = require("firebase/firestore");
6625
+
6626
+ // src/services/calendar/utils/docs.utils.ts
6627
+ var import_firestore25 = require("firebase/firestore");
6628
+ function getPractitionerCalendarEventDocRef(db, practitionerId, eventId) {
6629
+ return (0, import_firestore25.doc)(
6630
+ db,
6631
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${CALENDAR_COLLECTION}/${eventId}`
6632
+ );
6633
+ }
6634
+ function getPatientCalendarEventDocRef(db, patientId, eventId) {
6635
+ return (0, import_firestore25.doc)(
6636
+ db,
6637
+ `${PATIENTS_COLLECTION}/${patientId}/${CALENDAR_COLLECTION}/${eventId}`
6638
+ );
6639
+ }
6640
+ function getClinicCalendarEventDocRef(db, clinicId, eventId) {
6641
+ return (0, import_firestore25.doc)(
6642
+ db,
6643
+ `${CLINICS_COLLECTION}/${clinicId}/${CALENDAR_COLLECTION}/${eventId}`
6644
+ );
6645
+ }
6646
+ function getPractitionerSyncedCalendarDocRef(db, practitionerId, syncedCalendarId) {
6647
+ return (0, import_firestore25.doc)(
6648
+ db,
6649
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/syncedCalendars/${syncedCalendarId}`
6650
+ );
6651
+ }
6652
+ function getPatientSyncedCalendarDocRef(db, patientId, syncedCalendarId) {
6653
+ return (0, import_firestore25.doc)(
6654
+ db,
6655
+ `${PATIENTS_COLLECTION}/${patientId}/syncedCalendars/${syncedCalendarId}`
6656
+ );
6657
+ }
6658
+ function getClinicSyncedCalendarDocRef(db, clinicId, syncedCalendarId) {
6659
+ return (0, import_firestore25.doc)(
6660
+ db,
6661
+ `${CLINICS_COLLECTION}/${clinicId}/syncedCalendars/${syncedCalendarId}`
6662
+ );
6663
+ }
6664
+
6665
+ // src/services/calendar/utils/clinic.utils.ts
6666
+ async function createClinicCalendarEventUtil(db, clinicId, eventData, generateId2) {
6667
+ const eventId = generateId2();
6668
+ const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
6669
+ const newEvent = {
6670
+ id: eventId,
6671
+ ...eventData,
6672
+ createdAt: (0, import_firestore26.serverTimestamp)(),
6673
+ updatedAt: (0, import_firestore26.serverTimestamp)()
6674
+ };
6675
+ await (0, import_firestore26.setDoc)(eventRef, newEvent);
6676
+ return {
6677
+ ...newEvent,
6678
+ createdAt: import_firestore26.Timestamp.now(),
6679
+ updatedAt: import_firestore26.Timestamp.now()
6680
+ };
6681
+ }
6682
+ async function updateClinicCalendarEventUtil(db, clinicId, eventId, updateData) {
6683
+ const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
6684
+ const updates = {
6685
+ ...updateData,
6686
+ updatedAt: (0, import_firestore26.serverTimestamp)()
6687
+ };
6688
+ await (0, import_firestore26.updateDoc)(eventRef, updates);
6689
+ const updatedDoc = await (0, import_firestore26.getDoc)(eventRef);
6690
+ if (!updatedDoc.exists()) {
6691
+ throw new Error("Event not found after update");
6692
+ }
6693
+ return updatedDoc.data();
6694
+ }
6695
+ async function checkAutoConfirmAppointmentsUtil(db, clinicId) {
6696
+ const clinicDoc = await (0, import_firestore26.getDoc)((0, import_firestore26.doc)(db, `clinics/${clinicId}`));
6697
+ if (!clinicDoc.exists()) {
6698
+ throw new Error(`Clinic with ID ${clinicId} not found`);
6699
+ }
6700
+ const clinicData = clinicDoc.data();
6701
+ const clinicGroupId = clinicData.clinicGroupId;
6702
+ if (!clinicGroupId) {
6703
+ return false;
6704
+ }
6705
+ const clinicGroupDoc = await (0, import_firestore26.getDoc)(
6706
+ (0, import_firestore26.doc)(db, `${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}`)
6707
+ );
6708
+ if (!clinicGroupDoc.exists()) {
6709
+ return false;
6710
+ }
6711
+ const clinicGroupData = clinicGroupDoc.data();
6712
+ return !!clinicGroupData.autoConfirmAppointments;
6713
+ }
6714
+
6715
+ // src/services/calendar/utils/patient.utils.ts
6716
+ var import_firestore27 = require("firebase/firestore");
6717
+ async function createPatientCalendarEventUtil(db, patientId, eventData, generateId2) {
6718
+ const eventId = generateId2();
6719
+ const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
6720
+ const newEvent = {
6721
+ id: eventId,
6722
+ ...eventData,
6723
+ createdAt: (0, import_firestore27.serverTimestamp)(),
6724
+ updatedAt: (0, import_firestore27.serverTimestamp)()
6725
+ };
6726
+ await (0, import_firestore27.setDoc)(eventRef, newEvent);
6727
+ return {
6728
+ ...newEvent,
6729
+ createdAt: import_firestore27.Timestamp.now(),
6730
+ updatedAt: import_firestore27.Timestamp.now()
6731
+ };
6732
+ }
6733
+ async function updatePatientCalendarEventUtil(db, patientId, eventId, updateData) {
6734
+ const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
6735
+ const updates = {
6736
+ ...updateData,
6737
+ updatedAt: (0, import_firestore27.serverTimestamp)()
6738
+ };
6739
+ await (0, import_firestore27.updateDoc)(eventRef, updates);
6740
+ const updatedDoc = await (0, import_firestore27.getDoc)(eventRef);
6741
+ if (!updatedDoc.exists()) {
6742
+ throw new Error("Event not found after update");
6743
+ }
6744
+ return updatedDoc.data();
6745
+ }
6746
+
6747
+ // src/services/calendar/utils/practitioner.utils.ts
6748
+ var import_firestore28 = require("firebase/firestore");
6749
+ async function createPractitionerCalendarEventUtil(db, practitionerId, eventData, generateId2) {
6750
+ const eventId = generateId2();
6751
+ const eventRef = getPractitionerCalendarEventDocRef(
6752
+ db,
6753
+ practitionerId,
6754
+ eventId
6755
+ );
6756
+ const newEvent = {
6757
+ id: eventId,
6758
+ ...eventData,
6759
+ createdAt: (0, import_firestore28.serverTimestamp)(),
6760
+ updatedAt: (0, import_firestore28.serverTimestamp)()
6761
+ };
6762
+ await (0, import_firestore28.setDoc)(eventRef, newEvent);
6763
+ return {
6764
+ ...newEvent,
6765
+ createdAt: import_firestore28.Timestamp.now(),
6766
+ updatedAt: import_firestore28.Timestamp.now()
6767
+ };
6768
+ }
6769
+ async function updatePractitionerCalendarEventUtil(db, practitionerId, eventId, updateData) {
6770
+ const eventRef = getPractitionerCalendarEventDocRef(
6771
+ db,
6772
+ practitionerId,
6773
+ eventId
6774
+ );
6775
+ const updates = {
6776
+ ...updateData,
6777
+ updatedAt: (0, import_firestore28.serverTimestamp)()
6778
+ };
6779
+ await (0, import_firestore28.updateDoc)(eventRef, updates);
6780
+ const updatedDoc = await (0, import_firestore28.getDoc)(eventRef);
6781
+ if (!updatedDoc.exists()) {
6782
+ throw new Error("Event not found after update");
6783
+ }
6784
+ return updatedDoc.data();
6785
+ }
6786
+
6787
+ // src/services/calendar/utils/appointment.utils.ts
6788
+ async function createAppointmentUtil(db, clinicId, practitionerId, patientId, eventData, generateId2) {
6789
+ const eventId = generateId2();
6790
+ const autoConfirm = await checkAutoConfirmAppointmentsUtil(db, clinicId);
6791
+ const initialStatus = autoConfirm ? "confirmed" /* CONFIRMED */ : "pending" /* PENDING */;
6792
+ const appointmentData = {
6793
+ ...eventData,
6794
+ clinicBranchId: clinicId,
6795
+ practitionerProfileId: practitionerId,
6796
+ patientProfileId: patientId,
6797
+ eventType: "appointment" /* APPOINTMENT */,
6798
+ status: eventData.status || initialStatus
6799
+ };
6800
+ const clinicPromise = createClinicCalendarEventUtil(
6801
+ db,
6802
+ clinicId,
6803
+ appointmentData,
6804
+ () => eventId
6805
+ // Use the same ID for all calendars
6806
+ );
6807
+ const practitionerPromise = createPractitionerCalendarEventUtil(
6808
+ db,
6809
+ practitionerId,
6810
+ appointmentData,
6811
+ () => eventId
6812
+ // Use the same ID for all calendars
6813
+ );
6814
+ const patientPromise = createPatientCalendarEventUtil(
6815
+ db,
6816
+ patientId,
6817
+ appointmentData,
6818
+ () => eventId
6819
+ // Use the same ID for all calendars
6820
+ );
6821
+ const [clinicEvent] = await Promise.all([
6822
+ clinicPromise,
6823
+ practitionerPromise,
6824
+ patientPromise
6825
+ ]);
6826
+ return clinicEvent;
6827
+ }
6828
+ async function updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, updateData) {
6829
+ const clinicPromise = updateClinicCalendarEventUtil(
6830
+ db,
6831
+ clinicId,
6832
+ eventId,
6833
+ updateData
6834
+ );
6835
+ const practitionerPromise = updatePractitionerCalendarEventUtil(
6836
+ db,
6837
+ practitionerId,
6838
+ eventId,
6839
+ updateData
6840
+ );
6841
+ const patientPromise = updatePatientCalendarEventUtil(
6842
+ db,
6843
+ patientId,
6844
+ eventId,
6845
+ updateData
6846
+ );
6847
+ const [clinicEvent] = await Promise.all([
6848
+ clinicPromise,
6849
+ practitionerPromise,
6850
+ patientPromise
6851
+ ]);
6852
+ return clinicEvent;
6853
+ }
6854
+
6855
+ // src/services/calendar/utils/synced-calendar.utils.ts
6856
+ var import_firestore29 = require("firebase/firestore");
6857
+ async function createPractitionerSyncedCalendarUtil(db, practitionerId, calendarData, generateId2) {
6858
+ const calendarId = generateId2();
6859
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
6860
+ db,
6861
+ practitionerId,
6862
+ calendarId
6863
+ );
6864
+ const newCalendar = {
6865
+ id: calendarId,
6866
+ ...calendarData,
6867
+ createdAt: (0, import_firestore29.serverTimestamp)(),
6868
+ updatedAt: (0, import_firestore29.serverTimestamp)()
6869
+ };
6870
+ await (0, import_firestore29.setDoc)(calendarRef, newCalendar);
6871
+ return {
6872
+ ...newCalendar,
6873
+ createdAt: import_firestore29.Timestamp.now(),
6874
+ updatedAt: import_firestore29.Timestamp.now()
6875
+ };
6876
+ }
6877
+ async function createPatientSyncedCalendarUtil(db, patientId, calendarData, generateId2) {
6878
+ const calendarId = generateId2();
6879
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
6880
+ const newCalendar = {
6881
+ id: calendarId,
6882
+ ...calendarData,
6883
+ createdAt: (0, import_firestore29.serverTimestamp)(),
6884
+ updatedAt: (0, import_firestore29.serverTimestamp)()
6885
+ };
6886
+ await (0, import_firestore29.setDoc)(calendarRef, newCalendar);
6887
+ return {
6888
+ ...newCalendar,
6889
+ createdAt: import_firestore29.Timestamp.now(),
6890
+ updatedAt: import_firestore29.Timestamp.now()
6891
+ };
6892
+ }
6893
+ async function createClinicSyncedCalendarUtil(db, clinicId, calendarData, generateId2) {
6894
+ const calendarId = generateId2();
6895
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
6896
+ const newCalendar = {
6897
+ id: calendarId,
6898
+ ...calendarData,
6899
+ createdAt: (0, import_firestore29.serverTimestamp)(),
6900
+ updatedAt: (0, import_firestore29.serverTimestamp)()
6901
+ };
6902
+ await (0, import_firestore29.setDoc)(calendarRef, newCalendar);
6903
+ return {
6904
+ ...newCalendar,
6905
+ createdAt: import_firestore29.Timestamp.now(),
6906
+ updatedAt: import_firestore29.Timestamp.now()
6907
+ };
6908
+ }
6909
+ async function getPractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
6910
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
6911
+ db,
6912
+ practitionerId,
6913
+ calendarId
6914
+ );
6915
+ const calendarDoc = await (0, import_firestore29.getDoc)(calendarRef);
6916
+ if (!calendarDoc.exists()) {
6917
+ return null;
6918
+ }
6919
+ return calendarDoc.data();
6920
+ }
6921
+ async function getPractitionerSyncedCalendarsUtil(db, practitionerId) {
6922
+ const calendarsRef = (0, import_firestore29.collection)(
6923
+ db,
6924
+ `practitioners/${practitionerId}/${SYNCED_CALENDARS_COLLECTION}`
6925
+ );
6926
+ const q = (0, import_firestore29.query)(calendarsRef, (0, import_firestore29.orderBy)("createdAt", "desc"));
6927
+ const querySnapshot = await (0, import_firestore29.getDocs)(q);
6928
+ return querySnapshot.docs.map((doc20) => doc20.data());
6929
+ }
6930
+ async function getPatientSyncedCalendarUtil(db, patientId, calendarId) {
6931
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
6932
+ const calendarDoc = await (0, import_firestore29.getDoc)(calendarRef);
6933
+ if (!calendarDoc.exists()) {
6934
+ return null;
6935
+ }
6936
+ return calendarDoc.data();
6937
+ }
6938
+ async function getPatientSyncedCalendarsUtil(db, patientId) {
6939
+ const calendarsRef = (0, import_firestore29.collection)(
6940
+ db,
6941
+ `patients/${patientId}/${SYNCED_CALENDARS_COLLECTION}`
6942
+ );
6943
+ const q = (0, import_firestore29.query)(calendarsRef, (0, import_firestore29.orderBy)("createdAt", "desc"));
6944
+ const querySnapshot = await (0, import_firestore29.getDocs)(q);
6945
+ return querySnapshot.docs.map((doc20) => doc20.data());
6946
+ }
6947
+ async function getClinicSyncedCalendarUtil(db, clinicId, calendarId) {
6948
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
6949
+ const calendarDoc = await (0, import_firestore29.getDoc)(calendarRef);
6950
+ if (!calendarDoc.exists()) {
6951
+ return null;
6952
+ }
6953
+ return calendarDoc.data();
6954
+ }
6955
+ async function getClinicSyncedCalendarsUtil(db, clinicId) {
6956
+ const calendarsRef = (0, import_firestore29.collection)(
6957
+ db,
6958
+ `clinics/${clinicId}/${SYNCED_CALENDARS_COLLECTION}`
6959
+ );
6960
+ const q = (0, import_firestore29.query)(calendarsRef, (0, import_firestore29.orderBy)("createdAt", "desc"));
6961
+ const querySnapshot = await (0, import_firestore29.getDocs)(q);
6962
+ return querySnapshot.docs.map((doc20) => doc20.data());
6963
+ }
6964
+ async function updatePractitionerSyncedCalendarUtil(db, practitionerId, calendarId, updateData) {
6965
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
6966
+ db,
6967
+ practitionerId,
6968
+ calendarId
6969
+ );
6970
+ const updates = {
6971
+ ...updateData,
6972
+ updatedAt: (0, import_firestore29.serverTimestamp)()
6973
+ };
6974
+ await (0, import_firestore29.updateDoc)(calendarRef, updates);
6975
+ const updatedDoc = await (0, import_firestore29.getDoc)(calendarRef);
6976
+ if (!updatedDoc.exists()) {
6977
+ throw new Error("Synced calendar not found after update");
6978
+ }
6979
+ return updatedDoc.data();
6980
+ }
6981
+ async function updatePatientSyncedCalendarUtil(db, patientId, calendarId, updateData) {
6982
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
6983
+ const updates = {
6984
+ ...updateData,
6985
+ updatedAt: (0, import_firestore29.serverTimestamp)()
6986
+ };
6987
+ await (0, import_firestore29.updateDoc)(calendarRef, updates);
6988
+ const updatedDoc = await (0, import_firestore29.getDoc)(calendarRef);
6989
+ if (!updatedDoc.exists()) {
6990
+ throw new Error("Synced calendar not found after update");
6991
+ }
6992
+ return updatedDoc.data();
6993
+ }
6994
+ async function updateClinicSyncedCalendarUtil(db, clinicId, calendarId, updateData) {
6995
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
6996
+ const updates = {
6997
+ ...updateData,
6998
+ updatedAt: (0, import_firestore29.serverTimestamp)()
6999
+ };
7000
+ await (0, import_firestore29.updateDoc)(calendarRef, updates);
7001
+ const updatedDoc = await (0, import_firestore29.getDoc)(calendarRef);
7002
+ if (!updatedDoc.exists()) {
7003
+ throw new Error("Synced calendar not found after update");
7004
+ }
7005
+ return updatedDoc.data();
7006
+ }
7007
+ async function deletePractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
7008
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7009
+ db,
7010
+ practitionerId,
7011
+ calendarId
7012
+ );
7013
+ await (0, import_firestore29.deleteDoc)(calendarRef);
7014
+ }
7015
+ async function deletePatientSyncedCalendarUtil(db, patientId, calendarId) {
7016
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7017
+ await (0, import_firestore29.deleteDoc)(calendarRef);
7018
+ }
7019
+ async function deleteClinicSyncedCalendarUtil(db, clinicId, calendarId) {
7020
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7021
+ await (0, import_firestore29.deleteDoc)(calendarRef);
7022
+ }
7023
+ async function updateLastSyncedTimestampUtil(db, entityType, entityId, calendarId) {
7024
+ const updateData = {
7025
+ lastSyncedAt: import_firestore29.Timestamp.now()
7026
+ };
7027
+ switch (entityType) {
7028
+ case "practitioner":
7029
+ return updatePractitionerSyncedCalendarUtil(
7030
+ db,
7031
+ entityId,
7032
+ calendarId,
7033
+ updateData
7034
+ );
7035
+ case "patient":
7036
+ return updatePatientSyncedCalendarUtil(
7037
+ db,
7038
+ entityId,
7039
+ calendarId,
7040
+ updateData
7041
+ );
7042
+ case "clinic":
7043
+ return updateClinicSyncedCalendarUtil(
7044
+ db,
7045
+ entityId,
7046
+ calendarId,
7047
+ updateData
7048
+ );
7049
+ default:
7050
+ throw new Error(`Invalid entity type: ${entityType}`);
7051
+ }
7052
+ }
7053
+
7054
+ // src/services/calendar/utils/google-calendar.utils.ts
7055
+ var import_firestore30 = require("firebase/firestore");
7056
+ var GOOGLE_CALENDAR_API_URL = "https://www.googleapis.com/calendar/v3";
7057
+ var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
7058
+ var CLIENT_ID = "your-client-id";
7059
+ var CLIENT_SECRET = "your-client-secret";
7060
+ var REDIRECT_URI = "your-redirect-uri";
7061
+ async function makeRequest(method, url, headers, data, params) {
7062
+ const queryParams = params ? "?" + Object.entries(params).map(
7063
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
7064
+ ).join("&") : "";
7065
+ const finalUrl = url + queryParams;
7066
+ const options = {
7067
+ method,
7068
+ headers,
7069
+ body: data ? JSON.stringify(data) : void 0
7070
+ };
7071
+ const response = await fetch(finalUrl, options);
7072
+ if (!response.ok) {
7073
+ const error = new Error(
7074
+ `Request failed with status ${response.status}`
7075
+ );
7076
+ error.response = response;
7077
+ throw error;
7078
+ }
7079
+ return response.json();
7080
+ }
7081
+ async function authenticateWithGoogleCalendarUtil(authCode) {
7082
+ try {
7083
+ const data = {
7084
+ code: authCode,
7085
+ client_id: CLIENT_ID,
7086
+ client_secret: CLIENT_SECRET,
7087
+ redirect_uri: REDIRECT_URI,
7088
+ grant_type: "authorization_code"
7089
+ };
7090
+ const response = await makeRequest(
7091
+ "post",
7092
+ GOOGLE_OAUTH_URL,
7093
+ { "Content-Type": "application/json" },
7094
+ data
7095
+ );
7096
+ return {
7097
+ accessToken: response.access_token,
7098
+ refreshToken: response.refresh_token,
7099
+ expiresIn: response.expires_in
7100
+ };
7101
+ } catch (error) {
7102
+ const apiError = error;
7103
+ console.error(
7104
+ "Error authenticating with Google Calendar:",
7105
+ apiError.message || "Unknown error"
7106
+ );
7107
+ throw new Error(
7108
+ `Failed to authenticate with Google Calendar: ${apiError.message || "Unknown error"}`
7109
+ );
7110
+ }
7111
+ }
7112
+ async function refreshGoogleCalendarTokenUtil(refreshToken) {
7113
+ try {
7114
+ const data = {
7115
+ refresh_token: refreshToken,
7116
+ client_id: CLIENT_ID,
7117
+ client_secret: CLIENT_SECRET,
7118
+ grant_type: "refresh_token"
7119
+ };
7120
+ const response = await makeRequest(
7121
+ "post",
7122
+ GOOGLE_OAUTH_URL,
7123
+ { "Content-Type": "application/json" },
7124
+ data
7125
+ );
7126
+ return {
7127
+ accessToken: response.access_token,
7128
+ expiresIn: response.expires_in
7129
+ };
7130
+ } catch (error) {
7131
+ const apiError = error;
7132
+ console.error(
7133
+ "Error refreshing Google Calendar token:",
7134
+ apiError.message || "Unknown error"
7135
+ );
7136
+ throw new Error(
7137
+ `Failed to refresh Google Calendar token: ${apiError.message || "Unknown error"}`
7138
+ );
7139
+ }
7140
+ }
7141
+ async function listGoogleCalendarsUtil(accessToken) {
7142
+ try {
7143
+ const response = await makeRequest(
7144
+ "get",
7145
+ `${GOOGLE_CALENDAR_API_URL}/users/me/calendarList`,
7146
+ { Authorization: `Bearer ${accessToken}` }
7147
+ );
7148
+ return response.items.map((calendar) => ({
7149
+ id: calendar.id,
7150
+ name: calendar.summary
7151
+ }));
7152
+ } catch (error) {
7153
+ const apiError = error;
7154
+ console.error(
7155
+ "Error listing Google Calendars:",
7156
+ apiError.message || "Unknown error"
7157
+ );
7158
+ throw new Error(
7159
+ `Failed to list Google Calendars: ${apiError.message || "Unknown error"}`
7160
+ );
7161
+ }
7162
+ }
7163
+ async function ensureValidToken(db, entityType, entityId, syncedCalendar) {
7164
+ const expiryTime = syncedCalendar.tokenExpiry.toDate();
7165
+ const now = /* @__PURE__ */ new Date();
7166
+ const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
7167
+ if (expiryTime < fiveMinutesFromNow) {
7168
+ const { accessToken, expiresIn } = await refreshGoogleCalendarTokenUtil(
7169
+ syncedCalendar.refreshToken
7170
+ );
7171
+ const tokenExpiry = /* @__PURE__ */ new Date();
7172
+ tokenExpiry.setSeconds(tokenExpiry.getSeconds() + expiresIn);
7173
+ const updateData = {
7174
+ accessToken,
7175
+ tokenExpiry: import_firestore30.Timestamp.fromDate(tokenExpiry)
7176
+ };
7177
+ switch (entityType) {
7178
+ case "practitioner":
7179
+ await updatePractitionerSyncedCalendarUtil(
7180
+ db,
7181
+ entityId,
7182
+ syncedCalendar.id,
7183
+ updateData
7184
+ );
7185
+ break;
7186
+ case "patient":
7187
+ await updatePatientSyncedCalendarUtil(
7188
+ db,
7189
+ entityId,
7190
+ syncedCalendar.id,
7191
+ updateData
7192
+ );
7193
+ break;
7194
+ case "clinic":
7195
+ await updateClinicSyncedCalendarUtil(
7196
+ db,
7197
+ entityId,
7198
+ syncedCalendar.id,
7199
+ updateData
7200
+ );
7201
+ break;
7202
+ }
7203
+ return accessToken;
7204
+ }
7205
+ return syncedCalendar.accessToken;
7206
+ }
7207
+ async function syncEventsToGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, events, existingSyncId) {
7208
+ var _a, _b;
7209
+ try {
7210
+ const { accessToken } = await refreshGoogleCalendarTokenUtil(
7211
+ syncedCalendar.refreshToken
7212
+ );
7213
+ let syncedCount = 0;
7214
+ const errors = [];
7215
+ const eventIds = [];
7216
+ for (const event of events) {
7217
+ try {
7218
+ if (event.syncStatus === "external" /* EXTERNAL */) {
7219
+ continue;
7220
+ }
7221
+ if (entityType === "practitioner" && event.status !== "confirmed" /* CONFIRMED */) {
7222
+ continue;
7223
+ }
7224
+ if (entityType === "patient" && (event.status === "canceled" /* CANCELED */ || event.status === "rejected" /* REJECTED */)) {
7225
+ continue;
7226
+ }
7227
+ if (entityType === "clinic") {
7228
+ continue;
7229
+ }
7230
+ const googleEvent = convertCalendarEventToGoogleEventUtil(event);
7231
+ const headers = {
7232
+ Authorization: `Bearer ${accessToken}`,
7233
+ "Content-Type": "application/json"
7234
+ };
7235
+ let responseId = "";
7236
+ if (existingSyncId) {
7237
+ const response = await makeRequest(
7238
+ "put",
7239
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSyncId}`,
7240
+ headers,
7241
+ googleEvent
7242
+ );
7243
+ responseId = response.id;
7244
+ } else {
7245
+ const existingSync = (_a = event.syncedCalendarEventId) == null ? void 0 : _a.find(
7246
+ (sync) => sync.syncedCalendarProvider === "google" /* GOOGLE */ && // We should check if this is the same calendar we're syncing with, but that information isn't stored
7247
+ // For now, we'll just use the first Google Calendar sync ID
7248
+ sync.syncedCalendarProvider === syncedCalendar.provider
7249
+ );
7250
+ if (existingSync) {
7251
+ const response = await makeRequest(
7252
+ "put",
7253
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSync.eventId}`,
7254
+ headers,
7255
+ googleEvent
7256
+ );
7257
+ responseId = response.id;
7258
+ } else {
7259
+ const response = await makeRequest(
7260
+ "post",
7261
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
7262
+ headers,
7263
+ googleEvent
7264
+ );
7265
+ responseId = response.id;
7266
+ }
7267
+ }
7268
+ if (responseId) {
7269
+ eventIds.push(responseId);
7270
+ syncedCount++;
7271
+ }
7272
+ } catch (error) {
7273
+ const apiError = error;
7274
+ errors.push({
7275
+ eventId: event.id,
7276
+ error: apiError.message || "Unknown error",
7277
+ status: (_b = apiError.response) == null ? void 0 : _b.status
7278
+ });
7279
+ }
7280
+ }
7281
+ await updateLastSyncedTimestampUtil(
7282
+ db,
7283
+ entityType,
7284
+ entityId,
7285
+ syncedCalendar.id
7286
+ );
7287
+ return {
7288
+ success: errors.length === 0,
7289
+ syncedEvents: syncedCount,
7290
+ errors,
7291
+ eventIds
7292
+ };
7293
+ } catch (error) {
7294
+ console.error("Error syncing with Google Calendar:", error);
7295
+ return {
7296
+ success: false,
7297
+ syncedEvents: 0,
7298
+ errors: [{ error: error.message || "Unknown error" }],
7299
+ eventIds: []
7300
+ };
7301
+ }
7302
+ }
7303
+ async function fetchEventsFromGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, startDate, endDate) {
7304
+ try {
7305
+ const accessToken = await ensureValidToken(
7306
+ db,
7307
+ entityType,
7308
+ entityId,
7309
+ syncedCalendar
7310
+ );
7311
+ const timeMin = startDate.toISOString();
7312
+ const timeMax = endDate.toISOString();
7313
+ const response = await makeRequest(
7314
+ "get",
7315
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
7316
+ { Authorization: `Bearer ${accessToken}` },
7317
+ void 0,
7318
+ {
7319
+ timeMin,
7320
+ timeMax,
7321
+ singleEvents: "true",
7322
+ orderBy: "startTime"
7323
+ }
7324
+ );
7325
+ await updateLastSyncedTimestampUtil(
7326
+ db,
7327
+ entityType,
7328
+ entityId,
7329
+ syncedCalendar.id
7330
+ );
7331
+ return response.items;
7332
+ } catch (error) {
7333
+ const apiError = error;
7334
+ console.error(
7335
+ "Error fetching events from Google Calendar:",
7336
+ apiError.message || "Unknown error"
7337
+ );
7338
+ throw new Error(
7339
+ `Failed to fetch events from Google Calendar: ${apiError.message || "Unknown error"}`
7340
+ );
7341
+ }
7342
+ }
7343
+ function convertGoogleEventToCalendarEventUtil(googleEvent, entityId, entityType) {
7344
+ const start = googleEvent.start.dateTime ? new Date(googleEvent.start.dateTime) : new Date(googleEvent.start.date);
7345
+ const end = googleEvent.end.dateTime ? new Date(googleEvent.end.dateTime) : new Date(googleEvent.end.date);
7346
+ const calendarEvent = {
7347
+ eventName: googleEvent.summary || "External Event",
7348
+ eventLocation: googleEvent.location,
7349
+ eventTime: {
7350
+ start: import_firestore30.Timestamp.fromDate(start),
7351
+ end: import_firestore30.Timestamp.fromDate(end)
7352
+ },
7353
+ description: googleEvent.description || "",
7354
+ // External events are always set as CONFIRMED - status updates will happen externally
7355
+ status: "confirmed" /* CONFIRMED */,
7356
+ // All external events are marked as EXTERNAL to indicate they originated outside our system
7357
+ syncStatus: "external" /* EXTERNAL */,
7358
+ // All external events are treated as BLOCKING events
7359
+ eventType: "blocking" /* BLOCKING */,
7360
+ // Store the original Google Calendar event ID
7361
+ syncedCalendarEventId: [
7362
+ {
7363
+ eventId: googleEvent.id,
7364
+ syncedCalendarProvider: "google" /* GOOGLE */,
7365
+ syncedAt: import_firestore30.Timestamp.now()
7366
+ }
7367
+ ]
7368
+ };
7369
+ switch (entityType) {
7370
+ case "practitioner":
7371
+ calendarEvent.practitionerProfileId = entityId;
7372
+ break;
7373
+ case "patient":
7374
+ calendarEvent.patientProfileId = entityId;
7375
+ break;
7376
+ case "clinic":
7377
+ calendarEvent.clinicBranchId = entityId;
7378
+ break;
7379
+ }
7380
+ return calendarEvent;
7381
+ }
7382
+ function convertCalendarEventToGoogleEventUtil(calendarEvent) {
7383
+ const googleEvent = {
7384
+ summary: calendarEvent.eventName,
7385
+ location: calendarEvent.eventLocation,
7386
+ description: calendarEvent.description,
7387
+ start: {
7388
+ dateTime: calendarEvent.eventTime.start.toDate().toISOString(),
7389
+ timeZone: "UTC"
7390
+ },
7391
+ end: {
7392
+ dateTime: calendarEvent.eventTime.end.toDate().toISOString(),
7393
+ timeZone: "UTC"
7394
+ },
7395
+ // Add reminders
7396
+ reminders: {
7397
+ useDefault: false,
7398
+ overrides: [
7399
+ { method: "email", minutes: 24 * 60 },
7400
+ // 1 day before
7401
+ { method: "popup", minutes: 30 }
7402
+ // 30 minutes before
7403
+ ]
7404
+ }
7405
+ };
7406
+ switch (calendarEvent.status) {
7407
+ case "confirmed" /* CONFIRMED */:
7408
+ googleEvent.status = "confirmed";
7409
+ break;
7410
+ case "canceled" /* CANCELED */:
7411
+ googleEvent.status = "cancelled";
7412
+ break;
7413
+ case "pending" /* PENDING */:
7414
+ googleEvent.status = "tentative";
7415
+ break;
7416
+ default:
7417
+ googleEvent.status = "confirmed";
7418
+ }
7419
+ if (calendarEvent.eventType === "appointment" /* APPOINTMENT */) {
7420
+ googleEvent.attendees = [];
7421
+ if (calendarEvent.practitionerProfileId) {
7422
+ googleEvent.attendees.push({
7423
+ email: "practitioner@example.com",
7424
+ // This would be fetched from the practitioner profile
7425
+ displayName: "Dr. Practitioner",
7426
+ // This would be fetched from the practitioner profile
7427
+ responseStatus: "accepted"
7428
+ });
7429
+ }
7430
+ if (calendarEvent.patientProfileId) {
7431
+ googleEvent.attendees.push({
7432
+ email: "patient@example.com",
7433
+ // This would be fetched from the patient profile
7434
+ displayName: "Patient",
7435
+ // This would be fetched from the patient profile
7436
+ responseStatus: "needsAction"
7437
+ });
7438
+ }
7439
+ }
7440
+ return googleEvent;
7441
+ }
7442
+ async function deleteGoogleCalendarEventUtil(db, entityType, entityId, syncedCalendar, eventId) {
7443
+ try {
7444
+ const accessToken = await ensureValidToken(
7445
+ db,
7446
+ entityType,
7447
+ entityId,
7448
+ syncedCalendar
7449
+ );
7450
+ await makeRequest(
7451
+ "delete",
7452
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
7453
+ { Authorization: `Bearer ${accessToken}` }
7454
+ );
7455
+ return true;
7456
+ } catch (error) {
7457
+ const apiError = error;
7458
+ console.error(
7459
+ "Error deleting event from Google Calendar:",
7460
+ apiError.message || "Unknown error"
7461
+ );
7462
+ throw new Error(
7463
+ `Failed to delete event from Google Calendar: ${apiError.message || "Unknown error"}`
7464
+ );
7465
+ }
7466
+ }
7467
+ function getGoogleCalendarOAuthUrlUtil(scopes = ["https://www.googleapis.com/auth/calendar"]) {
7468
+ const scopeString = encodeURIComponent(scopes.join(" "));
7469
+ return `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
7470
+ REDIRECT_URI
7471
+ )}&response_type=code&scope=${scopeString}&access_type=offline&prompt=consent`;
7472
+ }
7473
+
7474
+ // src/services/calendar/synced-calendars.service.ts
7475
+ var SyncedCalendarsService = class extends BaseService {
7476
+ /**
7477
+ * Creates a new SyncedCalendarsService instance
7478
+ * @param db - Firestore instance
7479
+ * @param auth - Firebase Auth instance
7480
+ * @param app - Firebase App instance
7481
+ */
7482
+ constructor(db, auth, app) {
7483
+ super(db, auth, app);
7484
+ }
7485
+ // ===== Practitioner Synced Calendars =====
7486
+ /**
7487
+ * Creates a synced calendar for a practitioner
7488
+ * @param practitionerId - ID of the practitioner
7489
+ * @param calendarData - Synced calendar data
7490
+ * @returns Created synced calendar
7491
+ */
7492
+ async createPractitionerSyncedCalendar(practitionerId, calendarData) {
7493
+ return createPractitionerSyncedCalendarUtil(
7494
+ this.db,
7495
+ practitionerId,
7496
+ calendarData,
7497
+ this.generateId.bind(this)
7498
+ );
7499
+ }
7500
+ /**
7501
+ * Gets a synced calendar for a practitioner
7502
+ * @param practitionerId - ID of the practitioner
7503
+ * @param calendarId - ID of the synced calendar
7504
+ * @returns Synced calendar or null if not found
7505
+ */
7506
+ async getPractitionerSyncedCalendar(practitionerId, calendarId) {
7507
+ return getPractitionerSyncedCalendarUtil(
7508
+ this.db,
7509
+ practitionerId,
7510
+ calendarId
7511
+ );
7512
+ }
7513
+ /**
7514
+ * Gets all synced calendars for a practitioner
7515
+ * @param practitionerId - ID of the practitioner
7516
+ * @returns Array of synced calendars
7517
+ */
7518
+ async getPractitionerSyncedCalendars(practitionerId) {
7519
+ return getPractitionerSyncedCalendarsUtil(this.db, practitionerId);
7520
+ }
7521
+ /**
7522
+ * Updates a synced calendar for a practitioner
7523
+ * @param practitionerId - ID of the practitioner
7524
+ * @param calendarId - ID of the synced calendar
7525
+ * @param updateData - Data to update
7526
+ * @returns Updated synced calendar
7527
+ */
7528
+ async updatePractitionerSyncedCalendar(practitionerId, calendarId, updateData) {
7529
+ return updatePractitionerSyncedCalendarUtil(
7530
+ this.db,
7531
+ practitionerId,
7532
+ calendarId,
7533
+ updateData
7534
+ );
7535
+ }
7536
+ /**
7537
+ * Deletes a synced calendar for a practitioner
7538
+ * @param practitionerId - ID of the practitioner
7539
+ * @param calendarId - ID of the synced calendar
7540
+ */
7541
+ async deletePractitionerSyncedCalendar(practitionerId, calendarId) {
7542
+ return deletePractitionerSyncedCalendarUtil(
7543
+ this.db,
7544
+ practitionerId,
7545
+ calendarId
7546
+ );
7547
+ }
7548
+ // ===== Patient Synced Calendars =====
7549
+ /**
7550
+ * Creates a synced calendar for a patient
7551
+ * @param patientId - ID of the patient
7552
+ * @param calendarData - Synced calendar data
7553
+ * @returns Created synced calendar
7554
+ */
7555
+ async createPatientSyncedCalendar(patientId, calendarData) {
7556
+ return createPatientSyncedCalendarUtil(
7557
+ this.db,
7558
+ patientId,
7559
+ calendarData,
7560
+ this.generateId.bind(this)
7561
+ );
7562
+ }
7563
+ /**
7564
+ * Gets a synced calendar for a patient
7565
+ * @param patientId - ID of the patient
7566
+ * @param calendarId - ID of the synced calendar
7567
+ * @returns Synced calendar or null if not found
7568
+ */
7569
+ async getPatientSyncedCalendar(patientId, calendarId) {
7570
+ return getPatientSyncedCalendarUtil(this.db, patientId, calendarId);
7571
+ }
7572
+ /**
7573
+ * Gets all synced calendars for a patient
7574
+ * @param patientId - ID of the patient
7575
+ * @returns Array of synced calendars
7576
+ */
7577
+ async getPatientSyncedCalendars(patientId) {
7578
+ return getPatientSyncedCalendarsUtil(this.db, patientId);
7579
+ }
7580
+ /**
7581
+ * Updates a synced calendar for a patient
7582
+ * @param patientId - ID of the patient
7583
+ * @param calendarId - ID of the synced calendar
7584
+ * @param updateData - Data to update
7585
+ * @returns Updated synced calendar
7586
+ */
7587
+ async updatePatientSyncedCalendar(patientId, calendarId, updateData) {
7588
+ return updatePatientSyncedCalendarUtil(
7589
+ this.db,
7590
+ patientId,
7591
+ calendarId,
7592
+ updateData
7593
+ );
7594
+ }
7595
+ /**
7596
+ * Deletes a synced calendar for a patient
7597
+ * @param patientId - ID of the patient
7598
+ * @param calendarId - ID of the synced calendar
7599
+ */
7600
+ async deletePatientSyncedCalendar(patientId, calendarId) {
7601
+ return deletePatientSyncedCalendarUtil(this.db, patientId, calendarId);
7602
+ }
7603
+ // ===== Clinic Synced Calendars =====
7604
+ /**
7605
+ * Creates a synced calendar for a clinic
7606
+ * @param clinicId - ID of the clinic
7607
+ * @param calendarData - Synced calendar data
7608
+ * @returns Created synced calendar
7609
+ */
7610
+ async createClinicSyncedCalendar(clinicId, calendarData) {
7611
+ return createClinicSyncedCalendarUtil(
7612
+ this.db,
7613
+ clinicId,
7614
+ calendarData,
7615
+ this.generateId.bind(this)
7616
+ );
7617
+ }
7618
+ /**
7619
+ * Gets a synced calendar for a clinic
7620
+ * @param clinicId - ID of the clinic
7621
+ * @param calendarId - ID of the synced calendar
7622
+ * @returns Synced calendar or null if not found
7623
+ */
7624
+ async getClinicSyncedCalendar(clinicId, calendarId) {
7625
+ return getClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
7626
+ }
7627
+ /**
7628
+ * Gets all synced calendars for a clinic
7629
+ * @param clinicId - ID of the clinic
7630
+ * @returns Array of synced calendars
7631
+ */
7632
+ async getClinicSyncedCalendars(clinicId) {
7633
+ return getClinicSyncedCalendarsUtil(this.db, clinicId);
7634
+ }
7635
+ /**
7636
+ * Updates a synced calendar for a clinic
7637
+ * @param clinicId - ID of the clinic
7638
+ * @param calendarId - ID of the synced calendar
7639
+ * @param updateData - Data to update
7640
+ * @returns Updated synced calendar
7641
+ */
7642
+ async updateClinicSyncedCalendar(clinicId, calendarId, updateData) {
7643
+ return updateClinicSyncedCalendarUtil(
7644
+ this.db,
7645
+ clinicId,
7646
+ calendarId,
7647
+ updateData
7648
+ );
7649
+ }
7650
+ /**
7651
+ * Deletes a synced calendar for a clinic
7652
+ * @param clinicId - ID of the clinic
7653
+ * @param calendarId - ID of the synced calendar
7654
+ */
7655
+ async deleteClinicSyncedCalendar(clinicId, calendarId) {
7656
+ return deleteClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
7657
+ }
7658
+ // ===== Google Calendar Integration =====
7659
+ /**
7660
+ * Gets the OAuth URL for Google Calendar
7661
+ * @param scopes - OAuth scopes to request
7662
+ * @returns OAuth URL
7663
+ */
7664
+ getGoogleCalendarOAuthUrl(scopes = ["https://www.googleapis.com/auth/calendar"]) {
7665
+ return getGoogleCalendarOAuthUrlUtil(scopes);
7666
+ }
7667
+ /**
7668
+ * Authenticates with Google Calendar using an authorization code
7669
+ * @param authCode - Authorization code from Google OAuth
7670
+ * @returns Access token, refresh token, and expiration time
7671
+ */
7672
+ async authenticateWithGoogleCalendar(authCode) {
7673
+ return authenticateWithGoogleCalendarUtil(authCode);
7674
+ }
7675
+ /**
7676
+ * Lists available Google Calendars for a user
7677
+ * @param accessToken - Google API access token
7678
+ * @returns List of available calendars
7679
+ */
7680
+ async listGoogleCalendars(accessToken) {
7681
+ return listGoogleCalendarsUtil(accessToken);
7682
+ }
7683
+ /**
7684
+ * Syncs events from our system to Google Calendar for a practitioner
7685
+ * @param practitionerId - ID of the practitioner
7686
+ * @param calendarId - ID of the synced calendar
7687
+ * @param events - Events to sync
7688
+ * @param existingSyncId - Optional existing sync ID for updating an event
7689
+ * @returns Result of the sync operation
7690
+ */
7691
+ async syncPractitionerEventsToGoogleCalendar(practitionerId, calendarId, events, existingSyncId) {
7692
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
7693
+ practitionerId,
7694
+ calendarId
7695
+ );
7696
+ if (!syncedCalendar) {
7697
+ throw new Error("Synced calendar not found");
7698
+ }
7699
+ return syncEventsToGoogleCalendarUtil(
7700
+ this.db,
7701
+ "practitioner",
7702
+ practitionerId,
7703
+ syncedCalendar,
7704
+ events,
7705
+ existingSyncId
7706
+ );
7707
+ }
7708
+ /**
7709
+ * Syncs events from our system to Google Calendar for a patient
7710
+ * @param patientId - ID of the patient
7711
+ * @param calendarId - ID of the synced calendar
7712
+ * @param events - Events to sync
7713
+ * @param existingSyncId - Optional existing sync ID for updating an event
7714
+ * @returns Result of the sync operation
7715
+ */
7716
+ async syncPatientEventsToGoogleCalendar(patientId, calendarId, events, existingSyncId) {
7717
+ const syncedCalendar = await this.getPatientSyncedCalendar(
7718
+ patientId,
7719
+ calendarId
7720
+ );
7721
+ if (!syncedCalendar) {
7722
+ throw new Error("Synced calendar not found");
7723
+ }
7724
+ return syncEventsToGoogleCalendarUtil(
7725
+ this.db,
7726
+ "patient",
7727
+ patientId,
7728
+ syncedCalendar,
7729
+ events,
7730
+ existingSyncId
7731
+ );
7732
+ }
7733
+ /**
7734
+ * Syncs events from our system to Google Calendar for a clinic
7735
+ * @param clinicId - ID of the clinic
7736
+ * @param calendarId - ID of the synced calendar
7737
+ * @param events - Events to sync
7738
+ * @returns Result of the sync operation
7739
+ */
7740
+ async syncClinicEventsToGoogleCalendar(clinicId, calendarId, events) {
7741
+ const syncedCalendar = await this.getClinicSyncedCalendar(
7742
+ clinicId,
7743
+ calendarId
7744
+ );
7745
+ if (!syncedCalendar) {
7746
+ throw new Error("Synced calendar not found");
7747
+ }
7748
+ return syncEventsToGoogleCalendarUtil(
7749
+ this.db,
7750
+ "clinic",
7751
+ clinicId,
7752
+ syncedCalendar,
7753
+ events
7754
+ );
7755
+ }
7756
+ /**
7757
+ * Fetches events from Google Calendar for a practitioner
7758
+ * @param practitionerId - ID of the practitioner
7759
+ * @param calendarId - ID of the synced calendar
7760
+ * @param startDate - Start date for fetching events
7761
+ * @param endDate - End date for fetching events
7762
+ * @returns Events fetched from Google Calendar
7763
+ */
7764
+ async fetchEventsFromPractitionerGoogleCalendar(practitionerId, calendarId, startDate, endDate) {
7765
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
7766
+ practitionerId,
7767
+ calendarId
7768
+ );
7769
+ if (!syncedCalendar) {
7770
+ throw new Error("Synced calendar not found");
7771
+ }
7772
+ return fetchEventsFromGoogleCalendarUtil(
7773
+ this.db,
7774
+ "practitioner",
7775
+ practitionerId,
7776
+ syncedCalendar,
7777
+ startDate,
7778
+ endDate
7779
+ );
7780
+ }
7781
+ /**
7782
+ * Fetches events from Google Calendar for a patient
7783
+ * @param patientId - ID of the patient
7784
+ * @param calendarId - ID of the synced calendar
7785
+ * @param startDate - Start date for fetching events
7786
+ * @param endDate - End date for fetching events
7787
+ * @returns Events fetched from Google Calendar
7788
+ */
7789
+ async fetchEventsFromPatientGoogleCalendar(patientId, calendarId, startDate, endDate) {
7790
+ const syncedCalendar = await this.getPatientSyncedCalendar(
7791
+ patientId,
7792
+ calendarId
7793
+ );
7794
+ if (!syncedCalendar) {
7795
+ throw new Error("Synced calendar not found");
7796
+ }
7797
+ return fetchEventsFromGoogleCalendarUtil(
7798
+ this.db,
7799
+ "patient",
7800
+ patientId,
7801
+ syncedCalendar,
7802
+ startDate,
7803
+ endDate
7804
+ );
7805
+ }
7806
+ /**
7807
+ * Fetches events from Google Calendar for a clinic
7808
+ * @param clinicId - ID of the clinic
7809
+ * @param calendarId - ID of the synced calendar
7810
+ * @param startDate - Start date for fetching events
7811
+ * @param endDate - End date for fetching events
7812
+ * @returns Events fetched from Google Calendar
7813
+ */
7814
+ async fetchEventsFromClinicGoogleCalendar(clinicId, calendarId, startDate, endDate) {
7815
+ const syncedCalendar = await this.getClinicSyncedCalendar(
7816
+ clinicId,
7817
+ calendarId
7818
+ );
7819
+ if (!syncedCalendar) {
7820
+ throw new Error("Synced calendar not found");
7821
+ }
7822
+ return fetchEventsFromGoogleCalendarUtil(
7823
+ this.db,
7824
+ "clinic",
7825
+ clinicId,
7826
+ syncedCalendar,
7827
+ startDate,
7828
+ endDate
7829
+ );
7830
+ }
7831
+ /**
7832
+ * Deletes an event from Google Calendar for a practitioner
7833
+ * @param practitionerId - ID of the practitioner
7834
+ * @param calendarId - ID of the synced calendar
7835
+ * @param eventId - ID of the event in Google Calendar
7836
+ * @returns Success status
7837
+ */
7838
+ async deletePractitionerGoogleCalendarEvent(practitionerId, calendarId, eventId) {
7839
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
7840
+ practitionerId,
7841
+ calendarId
7842
+ );
7843
+ if (!syncedCalendar) {
7844
+ throw new Error("Synced calendar not found");
7845
+ }
7846
+ return deleteGoogleCalendarEventUtil(
7847
+ this.db,
7848
+ "practitioner",
7849
+ practitionerId,
7850
+ syncedCalendar,
7851
+ eventId
7852
+ );
7853
+ }
7854
+ /**
7855
+ * Deletes an event from Google Calendar for a patient
7856
+ * @param patientId - ID of the patient
7857
+ * @param calendarId - ID of the synced calendar
7858
+ * @param eventId - ID of the event in Google Calendar
7859
+ * @returns Success status
7860
+ */
7861
+ async deletePatientGoogleCalendarEvent(patientId, calendarId, eventId) {
7862
+ const syncedCalendar = await this.getPatientSyncedCalendar(
7863
+ patientId,
7864
+ calendarId
7865
+ );
7866
+ if (!syncedCalendar) {
7867
+ throw new Error("Synced calendar not found");
7868
+ }
7869
+ return deleteGoogleCalendarEventUtil(
7870
+ this.db,
7871
+ "patient",
7872
+ patientId,
7873
+ syncedCalendar,
7874
+ eventId
7875
+ );
7876
+ }
7877
+ /**
7878
+ * Deletes an event from Google Calendar for a clinic
7879
+ * @param clinicId - ID of the clinic
7880
+ * @param calendarId - ID of the synced calendar
7881
+ * @param eventId - ID of the event in Google Calendar
7882
+ * @returns Success status
7883
+ */
7884
+ async deleteClinicGoogleCalendarEvent(clinicId, calendarId, eventId) {
7885
+ const syncedCalendar = await this.getClinicSyncedCalendar(
7886
+ clinicId,
7887
+ calendarId
7888
+ );
7889
+ if (!syncedCalendar) {
7890
+ throw new Error("Synced calendar not found");
7891
+ }
7892
+ return deleteGoogleCalendarEventUtil(
7893
+ this.db,
7894
+ "clinic",
7895
+ clinicId,
7896
+ syncedCalendar,
7897
+ eventId
7898
+ );
7899
+ }
7900
+ /**
7901
+ * Converts Google Calendar events to our system's format for a practitioner
7902
+ * @param practitionerId - ID of the practitioner
7903
+ * @param googleEvents - Google Calendar events
7904
+ * @returns Converted calendar events
7905
+ */
7906
+ convertGoogleEventsToPractitionerEvents(practitionerId, googleEvents) {
7907
+ return googleEvents.map(
7908
+ (event) => convertGoogleEventToCalendarEventUtil(
7909
+ event,
7910
+ practitionerId,
7911
+ "practitioner"
7912
+ )
7913
+ );
7914
+ }
7915
+ /**
7916
+ * Converts Google Calendar events to our system's format for a patient
7917
+ * @param patientId - ID of the patient
7918
+ * @param googleEvents - Google Calendar events
7919
+ * @returns Converted calendar events
7920
+ */
7921
+ convertGoogleEventsToPatientEvents(patientId, googleEvents) {
7922
+ return googleEvents.map(
7923
+ (event) => convertGoogleEventToCalendarEventUtil(event, patientId, "patient")
7924
+ );
7925
+ }
7926
+ /**
7927
+ * Converts Google Calendar events to our system's format for a clinic
7928
+ * @param clinicId - ID of the clinic
7929
+ * @param googleEvents - Google Calendar events
7930
+ * @returns Converted calendar events
7931
+ */
7932
+ convertGoogleEventsToClinicEvents(clinicId, googleEvents) {
7933
+ return googleEvents.map(
7934
+ (event) => convertGoogleEventToCalendarEventUtil(event, clinicId, "clinic")
7935
+ );
7936
+ }
7937
+ /**
7938
+ * Fetches a single event from Google Calendar for a practitioner
7939
+ * @param practitionerId - ID of the practitioner
7940
+ * @param calendarId - ID of the synced calendar
7941
+ * @param eventId - ID of the event in Google Calendar
7942
+ * @returns The event data or null if not found
7943
+ */
7944
+ async fetchEventFromPractitionerGoogleCalendar(practitionerId, calendarId, eventId) {
7945
+ var _a;
7946
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
7947
+ practitionerId,
7948
+ calendarId
7949
+ );
7950
+ if (!syncedCalendar) {
7951
+ throw new Error("Synced calendar not found");
7952
+ }
7953
+ try {
7954
+ const { accessToken } = await refreshGoogleCalendarTokenUtil(
7955
+ syncedCalendar.refreshToken
7956
+ );
7957
+ const response = await makeRequest(
7958
+ "get",
7959
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
7960
+ { Authorization: `Bearer ${accessToken}` }
7961
+ );
7962
+ await updateLastSyncedTimestampUtil(
7963
+ this.db,
7964
+ "practitioner",
7965
+ practitionerId,
7966
+ syncedCalendar.id
7967
+ );
7968
+ return response;
7969
+ } catch (error) {
7970
+ if (((_a = error.response) == null ? void 0 : _a.status) === 404) {
7971
+ return null;
7972
+ }
7973
+ console.error(
7974
+ `Error fetching event from Google Calendar: ${error.message}`
7975
+ );
7976
+ throw error;
7977
+ }
7978
+ }
7979
+ };
7980
+
7981
+ // src/services/calendar/calendar-refactored.service.ts
7982
+ var MIN_APPOINTMENT_DURATION2 = 15;
7983
+ var CalendarServiceV2 = class extends BaseService {
7984
+ /**
7985
+ * Creates a new CalendarService instance
7986
+ * @param db - Firestore instance
7987
+ * @param auth - Firebase Auth instance
7988
+ * @param app - Firebase App instance
7989
+ */
7990
+ constructor(db, auth, app) {
7991
+ super(db, auth, app);
7992
+ this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
7993
+ }
7994
+ // #region Public API Methods
7995
+ /**
7996
+ * Creates a new appointment with proper validation and scheduling rules
7997
+ * @param params - Appointment creation parameters
7998
+ * @returns Created calendar event
7999
+ */
8000
+ async createAppointment(params) {
8001
+ await this.validateAppointmentParams(params);
8002
+ await this.validateClinicWorkingHours(params.clinicId, params.eventTime);
8003
+ await this.validateDoctorAvailability(
8004
+ params.doctorId,
8005
+ params.eventTime,
8006
+ params.clinicId
8007
+ );
8008
+ const { clinicInfo, practitionerInfo, patientInfo } = await this.fetchProfileInfoCards(
8009
+ params.clinicId,
8010
+ params.doctorId,
8011
+ params.patientId
8012
+ );
8013
+ const appointmentData = {
8014
+ clinicBranchId: params.clinicId,
8015
+ clinicBranchInfo: clinicInfo,
8016
+ practitionerProfileId: params.doctorId,
8017
+ practitionerProfileInfo: practitionerInfo,
8018
+ patientProfileId: params.patientId,
8019
+ patientProfileInfo: patientInfo,
8020
+ procedureId: params.procedureId,
8021
+ eventLocation: params.eventLocation,
8022
+ eventName: "Appointment",
8023
+ // TODO: Add procedure name when procedure model is available
8024
+ eventTime: params.eventTime,
8025
+ description: params.description || "",
8026
+ status: "pending" /* PENDING */,
8027
+ syncStatus: "internal" /* INTERNAL */,
8028
+ eventType: "appointment" /* APPOINTMENT */
8029
+ };
8030
+ const appointment = await createAppointmentUtil(
8031
+ this.db,
8032
+ params.clinicId,
8033
+ params.doctorId,
8034
+ params.patientId,
8035
+ appointmentData,
8036
+ this.generateId.bind(this)
8037
+ );
8038
+ await this.syncAppointmentWithExternalCalendars(appointment);
8039
+ return appointment;
8040
+ }
8041
+ /**
8042
+ * Updates an existing appointment
8043
+ * @param params - Appointment update parameters
8044
+ * @returns Updated calendar event
8045
+ */
8046
+ async updateAppointment(params) {
8047
+ await this.validateUpdatePermissions(params);
8048
+ const updateData = {
8049
+ eventTime: params.eventTime,
8050
+ description: params.description,
8051
+ status: params.status
8052
+ };
8053
+ const appointment = await updateAppointmentUtil(
8054
+ this.db,
8055
+ params.clinicId,
8056
+ params.doctorId,
8057
+ params.patientId,
8058
+ params.appointmentId,
8059
+ updateData
8060
+ );
8061
+ await this.syncAppointmentWithExternalCalendars(appointment);
8062
+ return appointment;
8063
+ }
8064
+ /**
8065
+ * Gets available appointment slots for a doctor at a clinic
8066
+ * @param clinicId - ID of the clinic
8067
+ * @param doctorId - ID of the doctor
8068
+ * @param date - Date to check availability for
8069
+ * @returns Array of available time slots
8070
+ */
8071
+ async getAvailableSlots(clinicId, doctorId, date) {
8072
+ const workingHours = await this.getClinicWorkingHours(clinicId, date);
8073
+ const doctorSchedule = await this.getDoctorSchedule(doctorId, date);
8074
+ const existingAppointments = await this.getDoctorAppointments(
8075
+ doctorId,
8076
+ date
8077
+ );
8078
+ return this.calculateAvailableSlots(
8079
+ workingHours,
8080
+ doctorSchedule,
8081
+ existingAppointments
8082
+ );
8083
+ }
8084
+ /**
8085
+ * Confirms an appointment
8086
+ * @param appointmentId - ID of the appointment
8087
+ * @param clinicId - ID of the clinic
8088
+ * @returns Confirmed calendar event
8089
+ */
8090
+ async confirmAppointment(appointmentId, clinicId) {
8091
+ return this.updateAppointmentStatus(
8092
+ appointmentId,
8093
+ clinicId,
8094
+ "confirmed" /* CONFIRMED */
8095
+ );
8096
+ }
8097
+ /**
8098
+ * Rejects an appointment
8099
+ * @param appointmentId - ID of the appointment
8100
+ * @param clinicId - ID of the clinic
8101
+ * @returns Rejected calendar event
8102
+ */
8103
+ async rejectAppointment(appointmentId, clinicId) {
8104
+ return this.updateAppointmentStatus(
8105
+ appointmentId,
8106
+ clinicId,
8107
+ "rejected" /* REJECTED */
8108
+ );
8109
+ }
8110
+ /**
8111
+ * Cancels an appointment
8112
+ * @param appointmentId - ID of the appointment
8113
+ * @param clinicId - ID of the clinic
8114
+ * @returns Canceled calendar event
8115
+ */
8116
+ async cancelAppointment(appointmentId, clinicId) {
8117
+ return this.updateAppointmentStatus(
8118
+ appointmentId,
8119
+ clinicId,
8120
+ "canceled" /* CANCELED */
8121
+ );
8122
+ }
8123
+ /**
8124
+ * Imports events from external calendars
8125
+ * @param entityType - Type of entity (practitioner or patient)
8126
+ * @param entityId - ID of the entity
8127
+ * @param startDate - Start date for fetching events
8128
+ * @param endDate - End date for fetching events
8129
+ * @returns Number of events imported
8130
+ */
8131
+ async importEventsFromExternalCalendars(entityType, entityId, startDate, endDate) {
8132
+ if (entityType === "patient") {
8133
+ return 0;
8134
+ }
8135
+ const syncedCalendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
8136
+ entityId
8137
+ );
8138
+ const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
8139
+ if (activeCalendars.length === 0) {
8140
+ return 0;
8141
+ }
8142
+ let importedEventsCount = 0;
8143
+ const currentTime = import_firestore31.Timestamp.now();
8144
+ for (const calendar of activeCalendars) {
8145
+ try {
8146
+ let externalEvents = [];
8147
+ if (calendar.provider === "google" /* GOOGLE */) {
8148
+ externalEvents = await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
8149
+ entityId,
8150
+ calendar.id,
8151
+ startDate,
8152
+ endDate
8153
+ );
8154
+ }
8155
+ for (const externalEvent of externalEvents) {
8156
+ try {
8157
+ const convertedEvent = this.syncedCalendarsService.convertGoogleEventsToPractitionerEvents(
8158
+ entityId,
8159
+ [externalEvent]
8160
+ )[0];
8161
+ if (!convertedEvent.eventTime) {
8162
+ continue;
8163
+ }
8164
+ const eventData = {
8165
+ // Ensure all required fields are set
8166
+ eventName: convertedEvent.eventName || "External Event",
8167
+ eventTime: convertedEvent.eventTime,
8168
+ description: convertedEvent.description || "",
8169
+ status: "confirmed" /* CONFIRMED */,
8170
+ syncStatus: "external" /* EXTERNAL */,
8171
+ eventType: "blocking" /* BLOCKING */,
8172
+ practitionerProfileId: entityId,
8173
+ syncedCalendarEventId: [
8174
+ {
8175
+ eventId: externalEvent.id,
8176
+ syncedCalendarProvider: calendar.provider,
8177
+ syncedAt: currentTime
8178
+ }
8179
+ ]
8180
+ };
8181
+ const doctorEvent = await this.createDoctorBlockingEvent(
8182
+ entityId,
8183
+ eventData
8184
+ );
8185
+ if (doctorEvent) {
8186
+ importedEventsCount++;
8187
+ }
8188
+ } catch (eventError) {
8189
+ console.error("Error importing event:", eventError);
8190
+ }
8191
+ }
8192
+ } catch (calendarError) {
8193
+ console.error(
8194
+ `Error fetching events from calendar ${calendar.id}:`,
8195
+ calendarError
8196
+ );
8197
+ }
8198
+ }
8199
+ return importedEventsCount;
8200
+ }
8201
+ /**
8202
+ * Creates a blocking event in a doctor's calendar
8203
+ * @param doctorId - ID of the doctor
8204
+ * @param eventData - Calendar event data
8205
+ * @returns Created calendar event
8206
+ */
8207
+ async createDoctorBlockingEvent(doctorId, eventData) {
8208
+ try {
8209
+ const eventId = this.generateId();
8210
+ const eventRef = (0, import_firestore32.doc)(
8211
+ this.db,
8212
+ PRACTITIONERS_COLLECTION,
8213
+ doctorId,
8214
+ CALENDAR_COLLECTION,
8215
+ eventId
8216
+ );
8217
+ const newEvent = {
8218
+ id: eventId,
8219
+ ...eventData,
8220
+ createdAt: (0, import_firestore31.serverTimestamp)(),
8221
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8222
+ };
8223
+ await (0, import_firestore32.setDoc)(eventRef, newEvent);
8224
+ return {
8225
+ ...newEvent,
8226
+ createdAt: import_firestore31.Timestamp.now(),
8227
+ updatedAt: import_firestore31.Timestamp.now()
8228
+ };
8229
+ } catch (error) {
8230
+ console.error(
8231
+ `Error creating blocking event for doctor ${doctorId}:`,
8232
+ error
8233
+ );
8234
+ return null;
8235
+ }
8236
+ }
8237
+ /**
8238
+ * Periodically syncs events from external calendars for doctors
8239
+ * This would be called via a scheduled Cloud Function
8240
+ * @param lookbackDays - Number of days to look back for events
8241
+ * @param lookforwardDays - Number of days to look forward for events
8242
+ */
8243
+ async synchronizeExternalCalendars(lookbackDays = 7, lookforwardDays = 30) {
8244
+ try {
8245
+ const practitionersRef = (0, import_firestore32.collection)(this.db, PRACTITIONERS_COLLECTION);
8246
+ const practitionersSnapshot = await (0, import_firestore32.getDocs)(practitionersRef);
8247
+ const startDate = /* @__PURE__ */ new Date();
8248
+ startDate.setDate(startDate.getDate() - lookbackDays);
8249
+ const endDate = /* @__PURE__ */ new Date();
8250
+ endDate.setDate(endDate.getDate() + lookforwardDays);
8251
+ const syncPromises = [];
8252
+ for (const docSnapshot of practitionersSnapshot.docs) {
8253
+ const practitionerId = docSnapshot.id;
8254
+ syncPromises.push(
8255
+ this.importEventsFromExternalCalendars(
8256
+ "doctor",
8257
+ practitionerId,
8258
+ startDate,
8259
+ endDate
8260
+ ).then((count) => {
8261
+ console.log(
8262
+ `Imported ${count} events for doctor ${practitionerId}`
8263
+ );
8264
+ }).catch((error) => {
8265
+ console.error(
8266
+ `Error importing events for doctor ${practitionerId}:`,
8267
+ error
8268
+ );
8269
+ })
8270
+ );
8271
+ syncPromises.push(
8272
+ this.updateExistingEventsFromExternalCalendars(
8273
+ practitionerId,
8274
+ startDate,
8275
+ endDate
8276
+ ).then((count) => {
8277
+ console.log(
8278
+ `Updated ${count} events for doctor ${practitionerId}`
8279
+ );
8280
+ }).catch((error) => {
8281
+ console.error(
8282
+ `Error updating events for doctor ${practitionerId}:`,
8283
+ error
8284
+ );
8285
+ })
8286
+ );
8287
+ }
8288
+ await Promise.all(syncPromises);
8289
+ console.log("Completed external calendar synchronization");
8290
+ } catch (error) {
8291
+ console.error("Error synchronizing external calendars:", error);
8292
+ }
8293
+ }
8294
+ /**
8295
+ * Updates existing events that were synced from external calendars
8296
+ * @param doctorId - ID of the doctor
8297
+ * @param startDate - Start date for fetching events
8298
+ * @param endDate - End date for fetching events
8299
+ * @returns Number of events updated
8300
+ */
8301
+ async updateExistingEventsFromExternalCalendars(doctorId, startDate, endDate) {
8302
+ var _a;
8303
+ try {
8304
+ const eventsRef = (0, import_firestore32.collection)(
8305
+ this.db,
8306
+ PRACTITIONERS_COLLECTION,
8307
+ doctorId,
8308
+ CALENDAR_COLLECTION
8309
+ );
8310
+ const q = (0, import_firestore32.query)(
8311
+ eventsRef,
8312
+ (0, import_firestore32.where)("syncStatus", "==", "external" /* EXTERNAL */),
8313
+ (0, import_firestore32.where)("eventTime.start", ">=", import_firestore31.Timestamp.fromDate(startDate)),
8314
+ (0, import_firestore32.where)("eventTime.start", "<=", import_firestore31.Timestamp.fromDate(endDate))
8315
+ );
8316
+ const eventsSnapshot = await (0, import_firestore32.getDocs)(q);
8317
+ const events = eventsSnapshot.docs.map((doc20) => ({
8318
+ id: doc20.id,
8319
+ ...doc20.data()
8320
+ }));
8321
+ const calendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
8322
+ doctorId
8323
+ );
8324
+ const activeCalendars = calendars.filter((cal) => cal.isActive);
8325
+ if (activeCalendars.length === 0 || events.length === 0) {
8326
+ return 0;
8327
+ }
8328
+ let updatedCount = 0;
8329
+ for (const event of events) {
8330
+ if (!((_a = event.syncedCalendarEventId) == null ? void 0 : _a.length)) continue;
8331
+ for (const syncId of event.syncedCalendarEventId) {
8332
+ const calendar = activeCalendars.find(
8333
+ (cal) => cal.provider === syncId.syncedCalendarProvider
8334
+ );
8335
+ if (!calendar) continue;
8336
+ if (syncId.syncedCalendarProvider === "google" /* GOOGLE */) {
8337
+ try {
8338
+ const externalEvent = await this.fetchExternalEvent(
8339
+ doctorId,
8340
+ calendar,
8341
+ syncId.eventId
8342
+ );
8343
+ if (externalEvent) {
8344
+ const externalStartTime = new Date(
8345
+ externalEvent.start.dateTime || externalEvent.start.date
8346
+ ).getTime();
8347
+ const externalEndTime = new Date(
8348
+ externalEvent.end.dateTime || externalEvent.end.date
8349
+ ).getTime();
8350
+ const localStartTime = event.eventTime.start.toDate().getTime();
8351
+ const localEndTime = event.eventTime.end.toDate().getTime();
8352
+ if (externalStartTime !== localStartTime || externalEndTime !== localEndTime || externalEvent.summary !== event.eventName || externalEvent.description !== event.description) {
8353
+ await this.updateLocalEventFromExternal(
8354
+ doctorId,
8355
+ event.id,
8356
+ externalEvent
8357
+ );
8358
+ updatedCount++;
8359
+ }
8360
+ } else {
8361
+ await this.updateEventStatus(
8362
+ doctorId,
8363
+ event.id,
8364
+ "canceled" /* CANCELED */
8365
+ );
8366
+ updatedCount++;
8367
+ }
8368
+ } catch (error) {
8369
+ console.error(
8370
+ `Error updating external event ${event.id}:`,
8371
+ error
8372
+ );
8373
+ }
8374
+ }
8375
+ }
8376
+ }
8377
+ return updatedCount;
8378
+ } catch (error) {
8379
+ console.error(
8380
+ "Error updating existing events from external calendars:",
8381
+ error
8382
+ );
8383
+ return 0;
8384
+ }
8385
+ }
8386
+ /**
8387
+ * Fetches a single external event from Google Calendar
8388
+ * @param doctorId - ID of the doctor
8389
+ * @param calendar - Calendar information
8390
+ * @param externalEventId - ID of the external event
8391
+ * @returns External event data or null if not found
8392
+ */
8393
+ async fetchExternalEvent(doctorId, calendar, externalEventId) {
8394
+ try {
8395
+ if (calendar.provider === "google" /* GOOGLE */) {
8396
+ const result = await this.syncedCalendarsService.fetchEventFromPractitionerGoogleCalendar(
8397
+ doctorId,
8398
+ calendar.id,
8399
+ externalEventId
8400
+ );
8401
+ return result;
8402
+ }
8403
+ return null;
8404
+ } catch (error) {
8405
+ console.error(`Error fetching external event ${externalEventId}:`, error);
8406
+ return null;
8407
+ }
8408
+ }
8409
+ /**
8410
+ * Updates a local event with data from an external event
8411
+ * @param doctorId - ID of the doctor
8412
+ * @param eventId - ID of the local event
8413
+ * @param externalEvent - External event data
8414
+ */
8415
+ async updateLocalEventFromExternal(doctorId, eventId, externalEvent) {
8416
+ try {
8417
+ const startTime = new Date(
8418
+ externalEvent.start.dateTime || externalEvent.start.date
8419
+ );
8420
+ const endTime = new Date(
8421
+ externalEvent.end.dateTime || externalEvent.end.date
8422
+ );
8423
+ const eventRef = (0, import_firestore32.doc)(
8424
+ this.db,
8425
+ PRACTITIONERS_COLLECTION,
8426
+ doctorId,
8427
+ CALENDAR_COLLECTION,
8428
+ eventId
8429
+ );
8430
+ await (0, import_firestore32.updateDoc)(eventRef, {
8431
+ eventName: externalEvent.summary || "External Event",
8432
+ eventTime: {
8433
+ start: import_firestore31.Timestamp.fromDate(startTime),
8434
+ end: import_firestore31.Timestamp.fromDate(endTime)
8435
+ },
8436
+ description: externalEvent.description || "",
8437
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8438
+ });
8439
+ console.log(`Updated local event ${eventId} from external event`);
8440
+ } catch (error) {
8441
+ console.error(
8442
+ `Error updating local event ${eventId} from external:`,
8443
+ error
8444
+ );
8445
+ }
8446
+ }
8447
+ /**
8448
+ * Updates an event's status
8449
+ * @param doctorId - ID of the doctor
8450
+ * @param eventId - ID of the event
8451
+ * @param status - New status
8452
+ */
8453
+ async updateEventStatus(doctorId, eventId, status) {
8454
+ try {
8455
+ const eventRef = (0, import_firestore32.doc)(
8456
+ this.db,
8457
+ PRACTITIONERS_COLLECTION,
8458
+ doctorId,
8459
+ CALENDAR_COLLECTION,
8460
+ eventId
8461
+ );
8462
+ await (0, import_firestore32.updateDoc)(eventRef, {
8463
+ status,
8464
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8465
+ });
8466
+ console.log(`Updated event ${eventId} status to ${status}`);
8467
+ } catch (error) {
8468
+ console.error(`Error updating event ${eventId} status:`, error);
8469
+ }
8470
+ }
8471
+ /**
8472
+ * Creates a scheduled job to periodically sync external calendars
8473
+ * Note: This would be implemented using Cloud Functions in a real application
8474
+ * This is a sample implementation to show how it could be set up
8475
+ * @param interval - Interval in hours
8476
+ */
8477
+ createScheduledSyncJob(interval = 3) {
8478
+ console.log(
8479
+ `Setting up scheduled calendar sync job every ${interval} hours`
8480
+ );
8481
+ }
8482
+ // #endregion
8483
+ // #region Private Helper Methods
8484
+ /**
8485
+ * Validates appointment creation parameters
8486
+ * @param params - Appointment parameters to validate
8487
+ * @throws Error if validation fails
8488
+ */
8489
+ async validateAppointmentParams(params) {
8490
+ await createAppointmentSchema.parseAsync(params);
8491
+ }
8492
+ /**
8493
+ * Validates if the event time falls within clinic working hours
8494
+ * @param clinicId - ID of the clinic
8495
+ * @param eventTime - Event time to validate
8496
+ * @throws Error if validation fails
8497
+ */
8498
+ async validateClinicWorkingHours(clinicId, eventTime) {
8499
+ const startDate = eventTime.start.toDate();
8500
+ const workingHours = await this.getClinicWorkingHours(clinicId, startDate);
8501
+ if (workingHours.length === 0) {
8502
+ throw new Error("Clinic is not open on this day");
8503
+ }
8504
+ const startTime = startDate;
8505
+ const endTime = eventTime.end.toDate();
8506
+ const isWithinWorkingHours = workingHours.some((slot) => {
8507
+ return slot.start <= startTime && slot.end >= endTime && slot.isAvailable;
8508
+ });
8509
+ if (!isWithinWorkingHours) {
8510
+ throw new Error("Appointment time is outside clinic working hours");
8511
+ }
8512
+ }
8513
+ /**
8514
+ * Validates if the doctor is available during the event time
8515
+ * @param doctorId - ID of the doctor
8516
+ * @param eventTime - Event time to validate
8517
+ * @param clinicId - ID of the clinic where the appointment is being booked
8518
+ * @throws Error if validation fails
8519
+ */
8520
+ async validateDoctorAvailability(doctorId, eventTime, clinicId) {
8521
+ var _a;
8522
+ const startDate = eventTime.start.toDate();
8523
+ const startTime = startDate;
8524
+ const endTime = eventTime.end.toDate();
8525
+ const practitionerRef = (0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, doctorId);
8526
+ const practitionerDoc = await (0, import_firestore32.getDoc)(practitionerRef);
8527
+ if (!practitionerDoc.exists()) {
8528
+ throw new Error(`Doctor with ID ${doctorId} not found`);
8529
+ }
8530
+ const practitioner = practitionerDoc.data();
8531
+ if (!practitioner.clinics.includes(clinicId)) {
8532
+ throw new Error("Doctor does not work at this clinic");
8533
+ }
8534
+ const clinicWorkingHours = (_a = practitioner.clinicWorkingHours) == null ? void 0 : _a.find(
8535
+ (hours) => hours.clinicId === clinicId && hours.isActive
8536
+ );
8537
+ if (!clinicWorkingHours) {
8538
+ throw new Error("Doctor does not have working hours set for this clinic");
8539
+ }
8540
+ const dayOfWeek = startDate.getDay();
8541
+ const dayKey = [
8542
+ "sunday",
8543
+ "monday",
8544
+ "tuesday",
8545
+ "wednesday",
8546
+ "thursday",
8547
+ "friday",
8548
+ "saturday"
8549
+ ][dayOfWeek];
8550
+ const daySchedule = clinicWorkingHours.workingHours[dayKey];
8551
+ if (!daySchedule) {
8552
+ throw new Error("Doctor is not working on this day at this clinic");
8553
+ }
8554
+ const [startHour, startMinute] = daySchedule.start.split(":").map(Number);
8555
+ const [endHour, endMinute] = daySchedule.end.split(":").map(Number);
8556
+ const scheduleStart = new Date(startDate);
8557
+ scheduleStart.setHours(startHour, startMinute, 0, 0);
8558
+ const scheduleEnd = new Date(startDate);
8559
+ scheduleEnd.setHours(endHour, endMinute, 0, 0);
8560
+ if (startTime < scheduleStart || endTime > scheduleEnd) {
8561
+ throw new Error(
8562
+ "Appointment time is outside doctor's working hours at this clinic"
8563
+ );
8564
+ }
8565
+ const appointments = await this.getDoctorAppointments(doctorId, startDate);
8566
+ const hasOverlap = appointments.some((appointment) => {
8567
+ const appointmentStart = appointment.eventTime.start.toDate();
8568
+ const appointmentEnd = appointment.eventTime.end.toDate();
8569
+ return startTime >= appointmentStart && startTime < appointmentEnd || endTime > appointmentStart && endTime <= appointmentEnd || startTime <= appointmentStart && endTime >= appointmentEnd;
8570
+ });
8571
+ if (hasOverlap) {
8572
+ throw new Error("Doctor has another appointment during this time");
8573
+ }
8574
+ }
8575
+ /**
8576
+ * Updates appointment status
8577
+ * @param appointmentId - ID of the appointment
8578
+ * @param clinicId - ID of the clinic
8579
+ * @param status - New status
8580
+ * @returns Updated calendar event
8581
+ */
8582
+ async updateAppointmentStatus(appointmentId, clinicId, status) {
8583
+ const appointmentRef = (0, import_firestore32.doc)(this.db, CALENDAR_COLLECTION, appointmentId);
8584
+ const appointmentDoc = await (0, import_firestore32.getDoc)(appointmentRef);
8585
+ if (!appointmentDoc.exists()) {
8586
+ throw new Error(`Appointment with ID ${appointmentId} not found`);
8587
+ }
8588
+ const appointment = appointmentDoc.data();
8589
+ if (appointment.clinicBranchId !== clinicId) {
8590
+ throw new Error("Appointment does not belong to the specified clinic");
8591
+ }
8592
+ this.validateStatusTransition(appointment.status, status);
8593
+ const updateParams = {
8594
+ appointmentId,
8595
+ clinicId,
8596
+ doctorId: appointment.practitionerProfileId || "",
8597
+ patientId: appointment.patientProfileId || "",
8598
+ status
8599
+ };
8600
+ await this.validateUpdatePermissions(updateParams);
8601
+ return this.updateAppointment(updateParams);
8602
+ }
8603
+ /**
8604
+ * Validates status transition
8605
+ * @param currentStatus - Current status
8606
+ * @param newStatus - New status
8607
+ * @throws Error if transition is invalid
8608
+ */
8609
+ validateStatusTransition(currentStatus, newStatus) {
8610
+ const validTransitions = {
8611
+ ["pending" /* PENDING */]: [
8612
+ "confirmed" /* CONFIRMED */,
8613
+ "rejected" /* REJECTED */,
8614
+ "canceled" /* CANCELED */
8615
+ ],
8616
+ ["confirmed" /* CONFIRMED */]: [
8617
+ "canceled" /* CANCELED */,
8618
+ "completed" /* COMPLETED */,
8619
+ "rescheduled" /* RESCHEDULED */
8620
+ ],
8621
+ ["rejected" /* REJECTED */]: [],
8622
+ ["canceled" /* CANCELED */]: [],
8623
+ ["rescheduled" /* RESCHEDULED */]: [
8624
+ "confirmed" /* CONFIRMED */,
8625
+ "canceled" /* CANCELED */
8626
+ ],
8627
+ ["completed" /* COMPLETED */]: []
8628
+ };
8629
+ if (!validTransitions[currentStatus].includes(newStatus)) {
8630
+ throw new Error(
8631
+ `Invalid status transition from ${currentStatus} to ${newStatus}`
8632
+ );
8633
+ }
8634
+ }
8635
+ /**
8636
+ * Syncs appointment with external calendars based on entity type and status
8637
+ * @param appointment - Calendar event to sync
8638
+ */
8639
+ async syncAppointmentWithExternalCalendars(appointment) {
8640
+ if (!appointment.practitionerProfileId || !appointment.patientProfileId) {
8641
+ return;
8642
+ }
8643
+ try {
8644
+ const [doctorCalendars, patientCalendars] = await Promise.all([
8645
+ this.syncedCalendarsService.getPractitionerSyncedCalendars(
8646
+ appointment.practitionerProfileId
8647
+ ),
8648
+ this.syncedCalendarsService.getPatientSyncedCalendars(
8649
+ appointment.patientProfileId
8650
+ )
8651
+ ]);
8652
+ const activeDoctorCalendars = doctorCalendars.filter(
8653
+ (cal) => cal.isActive
8654
+ );
8655
+ const activePatientCalendars = patientCalendars.filter(
8656
+ (cal) => cal.isActive
8657
+ );
8658
+ if (activeDoctorCalendars.length === 0 && activePatientCalendars.length === 0) {
8659
+ return;
8660
+ }
8661
+ if (appointment.syncStatus !== "internal" /* INTERNAL */) {
8662
+ return;
8663
+ }
8664
+ if (appointment.status === "confirmed" /* CONFIRMED */ && activeDoctorCalendars.length > 0) {
8665
+ await Promise.all(
8666
+ activeDoctorCalendars.map(
8667
+ (calendar) => this.syncEventToExternalCalendar(appointment, calendar, "doctor")
8668
+ )
8669
+ );
8670
+ }
8671
+ if (appointment.status !== "canceled" /* CANCELED */ && appointment.status !== "rejected" /* REJECTED */ && activePatientCalendars.length > 0) {
8672
+ await Promise.all(
8673
+ activePatientCalendars.map(
8674
+ (calendar) => this.syncEventToExternalCalendar(appointment, calendar, "patient")
8675
+ )
8676
+ );
8677
+ }
8678
+ } catch (error) {
8679
+ console.error("Error syncing with external calendars:", error);
8680
+ }
8681
+ }
8682
+ /**
8683
+ * Syncs a single event to an external calendar
8684
+ * @param appointment - Calendar event to sync
8685
+ * @param calendar - External calendar to sync with
8686
+ * @param entityType - Type of entity owning the calendar
8687
+ */
8688
+ async syncEventToExternalCalendar(appointment, calendar, entityType) {
8689
+ var _a, _b, _c, _d, _e;
8690
+ try {
8691
+ const eventToSync = { ...appointment };
8692
+ let eventTitle = appointment.eventName;
8693
+ const clinicName = ((_a = appointment.clinicBranchInfo) == null ? void 0 : _a.name) || "Clinic";
8694
+ if (entityType === "patient") {
8695
+ eventTitle = `[${appointment.status}] ${eventTitle} @ ${clinicName}`;
8696
+ } else {
8697
+ eventTitle = `${eventTitle} - Patient: ${((_b = appointment.patientProfileInfo) == null ? void 0 : _b.fullName) || "Unknown"} @ ${clinicName}`;
8698
+ }
8699
+ eventToSync.eventName = eventTitle;
8700
+ const existingSyncId = (_d = (_c = appointment.syncedCalendarEventId) == null ? void 0 : _c.find(
8701
+ (sync) => sync.syncedCalendarProvider === calendar.provider
8702
+ )) == null ? void 0 : _d.eventId;
8703
+ if (calendar.provider === "google" /* GOOGLE */) {
8704
+ const result = await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
8705
+ entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
8706
+ calendar.id,
8707
+ [eventToSync],
8708
+ existingSyncId
8709
+ // Pass existing sync ID if we have one
8710
+ );
8711
+ if (result.success && ((_e = result.eventIds) == null ? void 0 : _e.length) && !existingSyncId) {
8712
+ const newSyncEvent = {
8713
+ eventId: result.eventIds[0],
8714
+ syncedCalendarProvider: calendar.provider,
8715
+ syncedAt: import_firestore31.Timestamp.now()
8716
+ };
8717
+ await this.updateEventWithSyncId(
8718
+ entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
8719
+ entityType,
8720
+ appointment.id,
8721
+ newSyncEvent
8722
+ );
8723
+ }
8724
+ }
8725
+ } catch (error) {
8726
+ console.error(`Error syncing with ${entityType}'s calendar:`, error);
8727
+ }
8728
+ }
8729
+ /**
8730
+ * Updates an event with a new sync ID
8731
+ * @param entityId - ID of the entity (doctor or patient)
8732
+ * @param entityType - Type of entity
8733
+ * @param eventId - ID of the event
8734
+ * @param syncEvent - Sync event information
8735
+ */
8736
+ async updateEventWithSyncId(entityId, entityType, eventId, syncEvent) {
8737
+ try {
8738
+ const collectionPath = entityType === "doctor" ? `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}` : `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
8739
+ const eventRef = (0, import_firestore32.doc)(this.db, collectionPath, eventId);
8740
+ const eventDoc = await (0, import_firestore32.getDoc)(eventRef);
8741
+ if (eventDoc.exists()) {
8742
+ const event = eventDoc.data();
8743
+ const syncIds = [...event.syncedCalendarEventId || []];
8744
+ const existingSyncIndex = syncIds.findIndex(
8745
+ (sync) => sync.syncedCalendarProvider === syncEvent.syncedCalendarProvider
8746
+ );
8747
+ if (existingSyncIndex >= 0) {
8748
+ syncIds[existingSyncIndex] = syncEvent;
8749
+ } else {
8750
+ syncIds.push(syncEvent);
8751
+ }
8752
+ await (0, import_firestore32.updateDoc)(eventRef, {
8753
+ syncedCalendarEventId: syncIds,
8754
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8755
+ });
8756
+ console.log(
8757
+ `Updated event ${eventId} with sync ID ${syncEvent.eventId}`
8758
+ );
8759
+ }
8760
+ } catch (error) {
8761
+ console.error("Error updating event with sync ID:", error);
8762
+ }
8763
+ }
8764
+ /**
8765
+ * Validates update permissions and parameters
8766
+ * @param params - Update parameters to validate
8767
+ */
8768
+ async validateUpdatePermissions(params) {
8769
+ await updateAppointmentSchema.parseAsync(params);
8770
+ }
8771
+ /**
8772
+ * Gets clinic working hours for a specific date
8773
+ * @param clinicId - ID of the clinic
8774
+ * @param date - Date to get working hours for
8775
+ * @returns Working hours for the clinic
8776
+ */
8777
+ async getClinicWorkingHours(clinicId, date) {
8778
+ const clinicRef = (0, import_firestore32.doc)(this.db, CLINICS_COLLECTION, clinicId);
8779
+ const clinicDoc = await (0, import_firestore32.getDoc)(clinicRef);
8780
+ if (!clinicDoc.exists()) {
8781
+ throw new Error(`Clinic with ID ${clinicId} not found`);
8782
+ }
8783
+ const workingHours = [];
8784
+ const dayOfWeek = date.getDay();
8785
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
8786
+ return workingHours;
8787
+ }
8788
+ const workingDate = new Date(date);
8789
+ workingDate.setHours(9, 0, 0, 0);
8790
+ const startTime = new Date(workingDate);
8791
+ workingDate.setHours(17, 0, 0, 0);
8792
+ const endTime = new Date(workingDate);
8793
+ workingHours.push({
8794
+ start: startTime,
8795
+ end: endTime,
8796
+ isAvailable: true
8797
+ });
8798
+ return workingHours;
8799
+ }
8800
+ /**
8801
+ * Gets doctor's schedule for a specific date
8802
+ * @param doctorId - ID of the doctor
8803
+ * @param date - Date to get schedule for
8804
+ * @returns Doctor's schedule
8805
+ */
8806
+ async getDoctorSchedule(doctorId, date) {
8807
+ const practitionerRef = (0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, doctorId);
8808
+ const practitionerDoc = await (0, import_firestore32.getDoc)(practitionerRef);
8809
+ if (!practitionerDoc.exists()) {
8810
+ throw new Error(`Doctor with ID ${doctorId} not found`);
8811
+ }
8812
+ const schedule = [];
8813
+ const dayOfWeek = date.getDay();
8814
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
8815
+ return schedule;
8816
+ }
8817
+ const scheduleDate = new Date(date);
8818
+ scheduleDate.setHours(9, 0, 0, 0);
8819
+ const startTime = new Date(scheduleDate);
8820
+ scheduleDate.setHours(17, 0, 0, 0);
8821
+ const endTime = new Date(scheduleDate);
8822
+ schedule.push({
8823
+ start: startTime,
8824
+ end: endTime,
8825
+ isAvailable: true
8826
+ });
8827
+ return schedule;
8828
+ }
8829
+ /**
8830
+ * Gets doctor's appointments for a specific date
8831
+ * @param doctorId - ID of the doctor
8832
+ * @param date - Date to get appointments for
8833
+ * @returns Array of calendar events
8834
+ */
8835
+ async getDoctorAppointments(doctorId, date) {
8836
+ const startOfDay = new Date(date);
8837
+ startOfDay.setHours(0, 0, 0, 0);
8838
+ const endOfDay = new Date(date);
8839
+ endOfDay.setHours(23, 59, 59, 999);
8840
+ const appointmentsRef = (0, import_firestore32.collection)(this.db, CALENDAR_COLLECTION);
8841
+ const q = (0, import_firestore32.query)(
8842
+ appointmentsRef,
8843
+ (0, import_firestore32.where)("practitionerProfileId", "==", doctorId),
8844
+ (0, import_firestore32.where)("eventTime.start", ">=", import_firestore31.Timestamp.fromDate(startOfDay)),
8845
+ (0, import_firestore32.where)("eventTime.start", "<=", import_firestore31.Timestamp.fromDate(endOfDay)),
8846
+ (0, import_firestore32.where)("status", "in", [
8847
+ "confirmed" /* CONFIRMED */,
8848
+ "pending" /* PENDING */
8849
+ ])
8850
+ );
8851
+ const querySnapshot = await (0, import_firestore32.getDocs)(q);
8852
+ return querySnapshot.docs.map((doc20) => doc20.data());
8853
+ }
8854
+ /**
8855
+ * Calculates available time slots based on working hours, schedule and existing appointments
8856
+ * @param workingHours - Clinic working hours
8857
+ * @param doctorSchedule - Doctor's schedule
8858
+ * @param existingAppointments - Existing appointments
8859
+ * @returns Array of available time slots
8860
+ */
8861
+ calculateAvailableSlots(workingHours, doctorSchedule, existingAppointments) {
8862
+ const availableSlots = [];
8863
+ for (const workingHour of workingHours) {
8864
+ for (const scheduleSlot of doctorSchedule) {
8865
+ const overlapStart = new Date(
8866
+ Math.max(workingHour.start.getTime(), scheduleSlot.start.getTime())
8867
+ );
8868
+ const overlapEnd = new Date(
8869
+ Math.min(workingHour.end.getTime(), scheduleSlot.end.getTime())
8870
+ );
8871
+ if (overlapStart < overlapEnd && workingHour.isAvailable && scheduleSlot.isAvailable) {
8872
+ let slotStart = new Date(overlapStart);
8873
+ while (slotStart < overlapEnd) {
8874
+ const slotEnd = new Date(
8875
+ slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
8876
+ );
8877
+ const hasOverlap = existingAppointments.some((appointment) => {
8878
+ const appointmentStart = appointment.eventTime.start.toDate();
8879
+ const appointmentEnd = appointment.eventTime.end.toDate();
8880
+ return slotStart >= appointmentStart && slotStart < appointmentEnd || slotEnd > appointmentStart && slotEnd <= appointmentEnd;
8881
+ });
8882
+ if (!hasOverlap && slotEnd <= overlapEnd) {
8883
+ availableSlots.push({
8884
+ start: new Date(slotStart),
8885
+ end: new Date(slotEnd),
8886
+ isAvailable: true
8887
+ });
8888
+ }
8889
+ slotStart = new Date(
8890
+ slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
8891
+ );
8892
+ }
8893
+ }
8894
+ }
8895
+ }
8896
+ return availableSlots;
8897
+ }
8898
+ /**
8899
+ * Fetches and creates info cards for clinic, doctor, and patient profiles
8900
+ * @param clinicId - ID of the clinic
8901
+ * @param doctorId - ID of the doctor
8902
+ * @param patientId - ID of the patient
8903
+ * @returns Object containing info cards for all profiles
8904
+ */
8905
+ async fetchProfileInfoCards(clinicId, doctorId, patientId) {
8906
+ var _a;
8907
+ try {
8908
+ const [clinicDoc, practitionerDoc, patientDoc, patientSensitiveInfoDoc] = await Promise.all([
8909
+ (0, import_firestore32.getDoc)((0, import_firestore32.doc)(this.db, CLINICS_COLLECTION, clinicId)),
8910
+ (0, import_firestore32.getDoc)((0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, doctorId)),
8911
+ (0, import_firestore32.getDoc)((0, import_firestore32.doc)(this.db, PATIENTS_COLLECTION, patientId)),
8912
+ (0, import_firestore32.getDoc)(
8913
+ (0, import_firestore32.doc)(
8914
+ this.db,
8915
+ PATIENTS_COLLECTION,
8916
+ patientId,
8917
+ PATIENT_SENSITIVE_INFO_COLLECTION,
8918
+ patientId
8919
+ )
8920
+ )
8921
+ ]);
8922
+ const clinicInfo = clinicDoc.exists() ? {
8923
+ id: clinicDoc.id,
8924
+ featuredPhoto: clinicDoc.data().featuredPhoto || "",
8925
+ name: clinicDoc.data().name,
8926
+ description: clinicDoc.data().description || "",
8927
+ location: clinicDoc.data().location,
8928
+ contactInfo: clinicDoc.data().contactInfo
8929
+ } : null;
8930
+ const practitionerInfo = practitionerDoc.exists() ? {
8931
+ id: practitionerDoc.id,
8932
+ practitionerPhoto: practitionerDoc.data().basicInfo.profileImageUrl || null,
8933
+ name: `${practitionerDoc.data().basicInfo.firstName} ${practitionerDoc.data().basicInfo.lastName}`,
8934
+ email: practitionerDoc.data().basicInfo.email,
8935
+ phone: practitionerDoc.data().basicInfo.phoneNumber || null,
8936
+ certification: practitionerDoc.data().certification
8937
+ } : null;
8938
+ let patientInfo = null;
8939
+ if (patientSensitiveInfoDoc.exists()) {
8940
+ const sensitiveData = patientSensitiveInfoDoc.data();
8941
+ patientInfo = {
8942
+ id: patientId,
8943
+ fullName: `${sensitiveData.firstName} ${sensitiveData.lastName}`,
8944
+ email: sensitiveData.email || "",
8945
+ phone: sensitiveData.phoneNumber || null,
8946
+ dateOfBirth: sensitiveData.dateOfBirth || import_firestore31.Timestamp.now(),
8947
+ gender: sensitiveData.gender || "other" /* OTHER */
8948
+ };
8949
+ } else if (patientDoc.exists()) {
8950
+ patientInfo = {
8951
+ id: patientDoc.id,
8952
+ fullName: patientDoc.data().displayName,
8953
+ email: ((_a = patientDoc.data().contactInfo) == null ? void 0 : _a.email) || "",
8954
+ phone: patientDoc.data().phoneNumber || null,
8955
+ dateOfBirth: patientDoc.data().dateOfBirth || import_firestore31.Timestamp.now(),
8956
+ gender: patientDoc.data().gender || "other" /* OTHER */
8957
+ };
8958
+ }
8959
+ return {
8960
+ clinicInfo,
8961
+ practitionerInfo,
8962
+ patientInfo
8963
+ };
8964
+ } catch (error) {
8965
+ console.error("Error fetching profile info cards:", error);
8966
+ return {
8967
+ clinicInfo: null,
8968
+ practitionerInfo: null,
8969
+ patientInfo: null
8970
+ };
8971
+ }
8972
+ }
8973
+ // #endregion
8974
+ };
8975
+
8976
+ // src/validations/notification.schema.ts
8977
+ var import_zod18 = require("zod");
8978
+ var baseNotificationSchema = import_zod18.z.object({
8979
+ id: import_zod18.z.string().optional(),
8980
+ userId: import_zod18.z.string(),
8981
+ notificationTime: import_zod18.z.any(),
6380
8982
  // Timestamp
6381
- notificationType: import_zod16.z.nativeEnum(NotificationType),
6382
- notificationTokens: import_zod16.z.array(import_zod16.z.string()),
6383
- status: import_zod16.z.nativeEnum(NotificationStatus),
6384
- createdAt: import_zod16.z.any().optional(),
8983
+ notificationType: import_zod18.z.nativeEnum(NotificationType),
8984
+ notificationTokens: import_zod18.z.array(import_zod18.z.string()),
8985
+ status: import_zod18.z.nativeEnum(NotificationStatus),
8986
+ createdAt: import_zod18.z.any().optional(),
6385
8987
  // Timestamp
6386
- updatedAt: import_zod16.z.any().optional(),
8988
+ updatedAt: import_zod18.z.any().optional(),
6387
8989
  // Timestamp
6388
- title: import_zod16.z.string(),
6389
- body: import_zod16.z.string(),
6390
- isRead: import_zod16.z.boolean(),
6391
- userRole: import_zod16.z.nativeEnum(UserRole)
8990
+ title: import_zod18.z.string(),
8991
+ body: import_zod18.z.string(),
8992
+ isRead: import_zod18.z.boolean(),
8993
+ userRole: import_zod18.z.nativeEnum(UserRole)
6392
8994
  });
6393
8995
  var preRequirementNotificationSchema = baseNotificationSchema.extend({
6394
- notificationType: import_zod16.z.literal("preRequirement" /* PRE_REQUIREMENT */),
6395
- treatmentId: import_zod16.z.string(),
6396
- requirements: import_zod16.z.array(import_zod16.z.string()),
6397
- deadline: import_zod16.z.any()
8996
+ notificationType: import_zod18.z.literal("preRequirement" /* PRE_REQUIREMENT */),
8997
+ treatmentId: import_zod18.z.string(),
8998
+ requirements: import_zod18.z.array(import_zod18.z.string()),
8999
+ deadline: import_zod18.z.any()
6398
9000
  // Timestamp
6399
9001
  });
6400
9002
  var postRequirementNotificationSchema = baseNotificationSchema.extend({
6401
- notificationType: import_zod16.z.literal("postRequirement" /* POST_REQUIREMENT */),
6402
- treatmentId: import_zod16.z.string(),
6403
- requirements: import_zod16.z.array(import_zod16.z.string()),
6404
- deadline: import_zod16.z.any()
9003
+ notificationType: import_zod18.z.literal("postRequirement" /* POST_REQUIREMENT */),
9004
+ treatmentId: import_zod18.z.string(),
9005
+ requirements: import_zod18.z.array(import_zod18.z.string()),
9006
+ deadline: import_zod18.z.any()
6405
9007
  // Timestamp
6406
9008
  });
6407
9009
  var appointmentReminderNotificationSchema = baseNotificationSchema.extend({
6408
- notificationType: import_zod16.z.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
6409
- appointmentId: import_zod16.z.string(),
6410
- appointmentTime: import_zod16.z.any(),
9010
+ notificationType: import_zod18.z.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
9011
+ appointmentId: import_zod18.z.string(),
9012
+ appointmentTime: import_zod18.z.any(),
6411
9013
  // Timestamp
6412
- treatmentType: import_zod16.z.string(),
6413
- doctorName: import_zod16.z.string()
9014
+ treatmentType: import_zod18.z.string(),
9015
+ doctorName: import_zod18.z.string()
6414
9016
  });
6415
9017
  var appointmentNotificationSchema = baseNotificationSchema.extend({
6416
- notificationType: import_zod16.z.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
6417
- appointmentId: import_zod16.z.string(),
6418
- appointmentStatus: import_zod16.z.string(),
6419
- previousStatus: import_zod16.z.string(),
6420
- reason: import_zod16.z.string().optional()
9018
+ notificationType: import_zod18.z.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
9019
+ appointmentId: import_zod18.z.string(),
9020
+ appointmentStatus: import_zod18.z.string(),
9021
+ previousStatus: import_zod18.z.string(),
9022
+ reason: import_zod18.z.string().optional()
6421
9023
  });
6422
- var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
9024
+ var notificationSchema = import_zod18.z.discriminatedUnion("notificationType", [
6423
9025
  preRequirementNotificationSchema,
6424
9026
  postRequirementNotificationSchema,
6425
9027
  appointmentReminderNotificationSchema,
@@ -6432,9 +9034,14 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6432
9034
  AllergyType,
6433
9035
  AuthService,
6434
9036
  BlockingCondition,
9037
+ CALENDAR_COLLECTION,
6435
9038
  CLINICS_COLLECTION,
6436
9039
  CLINIC_ADMINS_COLLECTION,
6437
9040
  CLINIC_GROUPS_COLLECTION,
9041
+ CalendarEventStatus,
9042
+ CalendarEventType,
9043
+ CalendarServiceV2,
9044
+ CalendarSyncStatus,
6438
9045
  CertificationLevel,
6439
9046
  CertificationSpecialty,
6440
9047
  ClinicAdminService,
@@ -6475,7 +9082,10 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6475
9082
  PractitionerService,
6476
9083
  PricingMeasure,
6477
9084
  ProcedureFamily,
9085
+ SYNCED_CALENDARS_COLLECTION,
6478
9086
  SubscriptionModel,
9087
+ SyncedCalendarProvider,
9088
+ SyncedCalendarsService,
6479
9089
  TreatmentBenefit,
6480
9090
  USER_ERRORS,
6481
9091
  UserService,
@@ -6492,6 +9102,8 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6492
9102
  appointmentReminderNotificationSchema,
6493
9103
  baseNotificationSchema,
6494
9104
  blockingConditionSchema,
9105
+ calendarEventSchema,
9106
+ calendarEventTimeSchema,
6495
9107
  clinicAdminOptionsSchema,
6496
9108
  clinicAdminSchema,
6497
9109
  clinicAdminSignupSchema,
@@ -6507,6 +9119,8 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6507
9119
  contactPersonSchema,
6508
9120
  contraindicationSchema,
6509
9121
  createAdminTokenSchema,
9122
+ createAppointmentSchema,
9123
+ createCalendarEventSchema,
6510
9124
  createClinicAdminSchema,
6511
9125
  createClinicGroupSchema,
6512
9126
  createClinicSchema,
@@ -6538,21 +9152,30 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6538
9152
  patientDoctorSchema,
6539
9153
  patientLocationInfoSchema,
6540
9154
  patientMedicalInfoSchema,
9155
+ patientProfileInfoSchema,
6541
9156
  patientProfileSchema,
6542
9157
  patientSensitiveInfoSchema,
6543
9158
  postRequirementNotificationSchema,
6544
9159
  practitionerBasicInfoSchema,
6545
9160
  practitionerCertificationSchema,
6546
9161
  practitionerClinicProceduresSchema,
9162
+ practitionerClinicWorkingHoursSchema,
9163
+ practitionerProfileInfoSchema,
6547
9164
  practitionerReviewSchema,
6548
9165
  practitionerSchema,
6549
9166
  practitionerWorkingHoursSchema,
6550
9167
  preRequirementNotificationSchema,
9168
+ procedureCategorizationSchema,
9169
+ procedureInfoSchema,
6551
9170
  reviewInfoSchema,
6552
9171
  serviceInfoSchema,
9172
+ syncedCalendarEventSchema,
9173
+ timeSlotSchema,
6553
9174
  timestampSchema,
6554
9175
  updateAllergySchema,
9176
+ updateAppointmentSchema,
6555
9177
  updateBlockingConditionSchema,
9178
+ updateCalendarEventSchema,
6556
9179
  updateClinicAdminSchema,
6557
9180
  updateClinicGroupSchema,
6558
9181
  updateClinicSchema,